docs: 添加涉密文件自检工具实施计划
This commit is contained in:
0
UmiOCR-data/py_src/utils/__init__.py
Normal file
0
UmiOCR-data/py_src/utils/__init__.py
Normal file
64
UmiOCR-data/py_src/utils/app_opengl.py
Normal file
64
UmiOCR-data/py_src/utils/app_opengl.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# 软件渲染选项
|
||||
|
||||
from PySide2.QtGui import QGuiApplication, QOpenGLContext
|
||||
from PySide2.QtCore import Qt
|
||||
import os
|
||||
|
||||
from umi_log import logger
|
||||
from . import pre_configs
|
||||
from ..platform import Platform
|
||||
|
||||
_GLDict = {
|
||||
"AA_UseDesktopOpenGL": Qt.AA_UseDesktopOpenGL,
|
||||
"AA_UseOpenGLES": Qt.AA_UseOpenGLES,
|
||||
"AA_UseSoftwareOpenGL": Qt.AA_UseSoftwareOpenGL,
|
||||
}
|
||||
_Opt = ""
|
||||
|
||||
|
||||
def initOpengl():
|
||||
global _Opt
|
||||
opt = getOpengl()
|
||||
if opt not in _GLDict:
|
||||
opt = Platform.getOpenGLUse()
|
||||
setOpengl(opt)
|
||||
QGuiApplication.setAttribute(_GLDict[opt], True)
|
||||
_Opt = opt
|
||||
|
||||
|
||||
def checkOpengl():
|
||||
global _Opt
|
||||
if _Opt == "AA_UseOpenGLES": # GLES需要检查,有些win7不支持
|
||||
if not QOpenGLContext.openGLModuleType() == QOpenGLContext.LibGLES:
|
||||
QGuiApplication.setAttribute(Qt.AA_UseOpenGLES, False)
|
||||
_Opt = "AA_UseSoftwareOpenGL" # 既然不支持opengl,那就软渲染吧
|
||||
setOpengl(_Opt)
|
||||
msg = "当前系统不支持OpenGLES,已禁用此渲染器。\n若本次运行中程序崩溃或报错,请重新启动程序。\n\n"
|
||||
msg += "The current system does not support OpenGLES and has disabled the program from using this renderer. \nIf there are crashes or errors during this run, please restarting the program."
|
||||
logger.warning(msg)
|
||||
os.MessageBox(msg, type_="warning")
|
||||
|
||||
|
||||
def setOpengl(opt):
|
||||
if opt not in _GLDict:
|
||||
raise ValueError
|
||||
pre_configs.setValue("opengl", opt)
|
||||
|
||||
|
||||
def getOpengl():
|
||||
return pre_configs.getValue("opengl")
|
||||
|
||||
|
||||
# OpenGL渲染模式
|
||||
# 启用 OpenGL 上下文之间的资源共享
|
||||
# QGuiApplication.setAttribute(Qt.AA_ShareOpenGLContexts, True)
|
||||
# 渲染模式,【减少窗口调整大小时内容的抖动】
|
||||
# 方式一:启用OpenGL软件渲染。性能最差,CPU占用率大幅提升,效果最好。
|
||||
# QGuiApplication.setAttribute(Qt.AA_UseSoftwareOpenGL, True)
|
||||
# 方式二:使用 桌面 OpenGL(例如 opengl32.dll 或 libGL.so)。性能最好,效果较差。
|
||||
# QGuiApplication.setAttribute(Qt.AA_UseDesktopOpenGL, True)
|
||||
# 方式三:使用 OpenGL ES 2.0 或更高版本,用d3d接口抽象成Opengl。性能和效果都很好。但兼容性很差:
|
||||
# 1. ColorOverlay必须开启cache,否则无法渲染透明层。
|
||||
# 2. 需要系统安装dx9和OpenGL3。虚拟机中可能无法使用。需要检查兼容性!!!
|
||||
# 必须做兼容性判定,兼容时才启用AA_UseOpenGLES。
|
||||
# QGuiApplication.setAttribute(Qt.AA_UseOpenGLES, True)
|
||||
63
UmiOCR-data/py_src/utils/call_func.py
Normal file
63
UmiOCR-data/py_src/utils/call_func.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# 提供在主线程中调用指定函数
|
||||
|
||||
from PySide2.QtCore import QObject, Slot, Signal, QTimer, QMutex
|
||||
from uuid import uuid4 # 唯一ID
|
||||
|
||||
|
||||
class __CallFunc(QObject):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# 信号 在主线程中调用函数
|
||||
self._callFuncSignal = self.cSignal()
|
||||
self._callFuncSignal.signal.connect(self._cFunc)
|
||||
# 计时器停止字典
|
||||
self._timerStopDict = {}
|
||||
self._timerLock = QMutex()
|
||||
|
||||
# ========================= 【接口】 =========================
|
||||
|
||||
# 立刻:在主线程中调用python函数
|
||||
def now(self, func, *args):
|
||||
self._callFuncSignal.signal.emit((func, args))
|
||||
|
||||
# 延时:在主线程中调用python函数。返回计时器ID
|
||||
def delay(self, func, time, *args):
|
||||
timerID = str(uuid4())
|
||||
|
||||
def go():
|
||||
timer = QTimer(self)
|
||||
timer.setSingleShot(True) # 单次运行
|
||||
timer.timeout.connect(lambda: self._timerFunc(timerID, func, args))
|
||||
timer.start(time * 1000)
|
||||
|
||||
self.now(go)
|
||||
return timerID
|
||||
|
||||
# 取消已启用的延时
|
||||
def delayStop(self, timerID):
|
||||
self._timerLock.lock()
|
||||
self._timerStopDict[timerID] = True # 记录停止
|
||||
self._timerLock.unlock()
|
||||
|
||||
# ==================================================
|
||||
# 计时器调用的函数
|
||||
def _timerFunc(self, timerID, func, args):
|
||||
self._timerLock.lock()
|
||||
if timerID in self._timerStopDict:
|
||||
del self._timerStopDict[timerID]
|
||||
self._timerLock.unlock()
|
||||
return
|
||||
self._timerLock.unlock()
|
||||
func(*args)
|
||||
|
||||
# 异步调用的槽函数
|
||||
@Slot("QVariant")
|
||||
def _cFunc(self, args):
|
||||
args[0](*args[1])
|
||||
|
||||
# 信号类
|
||||
class cSignal(QObject):
|
||||
signal = Signal("QVariant")
|
||||
|
||||
|
||||
CallFunc = __CallFunc()
|
||||
131
UmiOCR-data/py_src/utils/file_finder.py
Normal file
131
UmiOCR-data/py_src/utils/file_finder.py
Normal file
@@ -0,0 +1,131 @@
|
||||
# ============================================
|
||||
# =============== 文件查找/加载 ===============
|
||||
# ============================================
|
||||
# 从指定路径中,查找符合的文件
|
||||
|
||||
import re
|
||||
import os
|
||||
import time
|
||||
from PySide2.QtQml import QJSValue
|
||||
from typing import List
|
||||
|
||||
from ..event_bus.pubsub_service import PubSubService # 发布事件
|
||||
from ..mission.mission_doc import MissionDOC, DocSuf
|
||||
from ..mission.mission_ocr import ImageSuf
|
||||
from umi_log import logger
|
||||
|
||||
|
||||
FileSuf = { # 合法文件后缀
|
||||
"image": ImageSuf,
|
||||
"doc": DocSuf,
|
||||
}
|
||||
|
||||
|
||||
# 同步从路径中搜索后缀符合要求的文件,返回路径列表。
|
||||
def findFiles(
|
||||
paths: List, # 初始路径列表
|
||||
sufType: str, # 后缀类型,FileSuf的key
|
||||
isRecurrence: bool, # 若为True,则递归搜索
|
||||
):
|
||||
if isinstance(paths, QJSValue):
|
||||
paths = paths.toVariant()
|
||||
if not isinstance(paths, list):
|
||||
logger.error(f"不合法的路径列表:{paths}, {type(paths)}")
|
||||
return []
|
||||
sufs = FileSuf.get(sufType, "")
|
||||
if not sufs:
|
||||
logger.error(f"不合法的后缀类型:{sufs}")
|
||||
return []
|
||||
|
||||
def _sufMatching(path):
|
||||
return os.path.splitext(path)[-1].lower() in sufs
|
||||
|
||||
filePaths = []
|
||||
for p in paths:
|
||||
if os.path.isfile(p) and _sufMatching(p): # 是文件,直接判断
|
||||
filePaths.append(os.path.abspath(p))
|
||||
elif os.path.isdir(p): # 是目录
|
||||
if isRecurrence: # 需要递归
|
||||
for root, dirs, files in os.walk(p):
|
||||
for file in files:
|
||||
if _sufMatching(file): # 收集子文件
|
||||
filePaths.append(
|
||||
os.path.abspath(os.path.join(root, file))
|
||||
) # 将路径转换为绝对路径
|
||||
else: # 不递归读取子文件夹
|
||||
for file in os.listdir(p):
|
||||
if os.path.isfile(os.path.join(p, file)) and _sufMatching(file):
|
||||
filePaths.append(os.path.abspath(os.path.join(p, file)))
|
||||
for i, p in enumerate(filePaths): # 规范化正斜杠
|
||||
filePaths[i] = p.replace("\\", "/")
|
||||
return filePaths
|
||||
|
||||
|
||||
# 异步从路径中搜索后缀符合要求的文件,并定时刷新UI。
|
||||
# image: 返回路径列表
|
||||
# doc: 返回 MissionDOC.getDocInfo 的信息字典列表
|
||||
def asynFindFiles(
|
||||
paths: List, # 初始路径列表
|
||||
sufType: str, # 后缀类型,FileSuf的key
|
||||
isRecurrence: bool, # 若为True,则递归搜索
|
||||
completeKey: str, # 全部完成后的事件key。向事件传入合法路径列表。
|
||||
updateKey: str, # UI刷新进度的事件key。填""则不刷新。向事件传入 (已完成的路径数量, 最近一条路径)
|
||||
updateTime: float, # UI刷新进度的间距
|
||||
):
|
||||
if isinstance(paths, QJSValue):
|
||||
paths = paths.toVariant()
|
||||
if not isinstance(paths, list):
|
||||
logger.error(f"不合法的路径列表:{paths}, {type(paths)}")
|
||||
PubSubService.publish(completeKey, [])
|
||||
return
|
||||
sufs = FileSuf.get(sufType, "")
|
||||
if not sufs:
|
||||
logger.error(f"不合法的后缀类型:{sufs}")
|
||||
PubSubService.publish(completeKey, [])
|
||||
return
|
||||
|
||||
def _sufMatching(path):
|
||||
return os.path.splitext(path)[-1].lower() in sufs
|
||||
|
||||
if not updateKey: # 如果没有刷新事件,则刷新间隔为无穷大
|
||||
updateTime = float("inf")
|
||||
filePaths = []
|
||||
lastTime = 0 # 上一次update事件的时间
|
||||
|
||||
def updateEvent(fp):
|
||||
nonlocal lastTime
|
||||
now = time.time()
|
||||
if now - lastTime > updateTime:
|
||||
PubSubService.publish(updateKey, len(filePaths), fp)
|
||||
lastTime = now
|
||||
|
||||
def addFile(fp):
|
||||
fp = fp.replace("\\", "/") # 规范化正斜杠
|
||||
if sufType == "doc": # 文档读取信息
|
||||
info = MissionDOC.getDocInfo(fp)
|
||||
if "error" in info:
|
||||
logger.warning(f'读入文档失败:{fp}, {info["error"]}')
|
||||
else:
|
||||
filePaths.append(info)
|
||||
else:
|
||||
filePaths.append(fp)
|
||||
updateEvent(fp)
|
||||
|
||||
for p in paths:
|
||||
if os.path.isfile(p) and _sufMatching(p): # 是文件,直接判断
|
||||
addFile(os.path.abspath(p))
|
||||
elif os.path.isdir(p): # 是目录
|
||||
if isRecurrence: # 需要递归
|
||||
for root, dirs, files in os.walk(p):
|
||||
for file in files:
|
||||
if _sufMatching(file): # 收集子文件
|
||||
# 转换为绝对路径
|
||||
fp = os.path.abspath(os.path.join(root, file))
|
||||
addFile(fp)
|
||||
else: # 不递归读取子文件夹
|
||||
for file in os.listdir(p):
|
||||
if os.path.isfile(os.path.join(p, file)) and _sufMatching(file):
|
||||
fp = os.path.abspath(os.path.join(p, file))
|
||||
addFile(fp)
|
||||
|
||||
PubSubService.publish(completeKey, filePaths)
|
||||
86
UmiOCR-data/py_src/utils/global_configs_connector.py
Normal file
86
UmiOCR-data/py_src/utils/global_configs_connector.py
Normal file
@@ -0,0 +1,86 @@
|
||||
# 全局设置连接器
|
||||
|
||||
import os
|
||||
from PySide2.QtCore import QObject, Slot
|
||||
|
||||
from . import app_opengl
|
||||
from .i18n_configs import I18n
|
||||
from ..platform import Platform
|
||||
from .pre_configs import getErrorStr
|
||||
from ..server import web_server
|
||||
from ..server.cmd_server import CmdActuator
|
||||
from umi_log import change_save_log_level, open_logs_dir
|
||||
|
||||
|
||||
class GlobalConfigsConnector(QObject):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# 创建快捷方式
|
||||
@Slot(str, result=str)
|
||||
def createShortcut(self, position):
|
||||
return Platform.Shortcut.createShortcut(position)
|
||||
|
||||
# 删除快捷方式
|
||||
@Slot(str, result=int)
|
||||
def deleteShortcut(self, position):
|
||||
return Platform.Shortcut.deleteShortcut(position)
|
||||
|
||||
# 获取UI语言信息
|
||||
@Slot(result="QVariant")
|
||||
def i18nGetInfos(self):
|
||||
return I18n.getInfos()
|
||||
|
||||
# 设置UI语言
|
||||
@Slot(str, result=bool)
|
||||
def i18nSetLanguage(self, lang):
|
||||
return I18n.setLanguage(lang)
|
||||
|
||||
# 获取Opengl渲染器选项
|
||||
@Slot(result=str)
|
||||
def getOpengl(self):
|
||||
return app_opengl.getOpengl()
|
||||
|
||||
# 设置Opengl渲染器选项
|
||||
@Slot(str)
|
||||
def setOpengl(self, opt):
|
||||
app_opengl.setOpengl(opt)
|
||||
|
||||
# 修改日志级别,成功返回T
|
||||
@Slot(str, result=bool)
|
||||
def change_save_log_level(self, levelname):
|
||||
return change_save_log_level(levelname)
|
||||
|
||||
# 打开日志保存目录
|
||||
@Slot()
|
||||
def open_logs_dir(self):
|
||||
open_logs_dir()
|
||||
|
||||
# 启动web服务器,传入qml对象及回调函数名。
|
||||
@Slot("QVariant", str, str, result=int)
|
||||
def runUmiWeb(self, qmlObj, callback, host):
|
||||
web_server.runUmiWeb(qmlObj, callback, host)
|
||||
|
||||
# 设置服务端口号
|
||||
@Slot(int)
|
||||
def setServerPort(self, port):
|
||||
web_server.setPort(port)
|
||||
|
||||
# 将qml模块字典传入cmd执行器
|
||||
@Slot("QVariant")
|
||||
def setQmlToCmd(self, moduleDict):
|
||||
CmdActuator.initCollect(moduleDict)
|
||||
|
||||
# 检查权限,返回检查结果
|
||||
@Slot(result=str)
|
||||
def checkAccess(self):
|
||||
cwd = os.getcwd() # 当前工作路径
|
||||
err = getErrorStr() # 读写异常情况
|
||||
if not err: # 没有异常,则再检查一遍权限
|
||||
if not os.access(cwd, os.R_OK):
|
||||
err += "在当前路径不具有可读权限。\nDo not have read permission on the current path."
|
||||
if not os.access(cwd, os.W_OK):
|
||||
err += "在当前路径不具有可写权限。\nDo not have write permission on the current path."
|
||||
if err:
|
||||
err = cwd + "\n" + err
|
||||
return err
|
||||
146
UmiOCR-data/py_src/utils/i18n_configs.py
Normal file
146
UmiOCR-data/py_src/utils/i18n_configs.py
Normal file
@@ -0,0 +1,146 @@
|
||||
# UI语言设置
|
||||
|
||||
import os
|
||||
from PySide2.QtCore import QTranslator
|
||||
|
||||
from . import pre_configs
|
||||
from plugin_i18n import setLangCode
|
||||
from umi_log import logger
|
||||
|
||||
I18nDir = "i18n" # 翻译文件 目录
|
||||
DefaultLang = "zh_CN" # 默认语言,项目中qsTr()标记的原生语言,无翻译文件。
|
||||
|
||||
# 语言表。每个语种只有第一个语言代码是有效的(对应到翻译文件.qm),
|
||||
# 其余的语言代码会映射到第一个。如zh_HK会映射到zh_TW,en_CA映射到en_US。
|
||||
# https://www.science.co.il/language/Locale-codes.php
|
||||
LanguageCodes = {
|
||||
# ===== 简中 =====
|
||||
"zh_CN": "简体中文",
|
||||
"zh": "简体中文",
|
||||
# ===== 繁中 =====
|
||||
"zh_TW": "繁體中文", # 中国台湾
|
||||
"zh_HK": "繁體中文", # 中国香港
|
||||
"zh_MO": "繁體中文", # 中国澳门
|
||||
"zh_SG": "繁體中文", # 新加坡
|
||||
# ===== 英语 =====
|
||||
"en_US": "English", # 美国
|
||||
"en": "English",
|
||||
"en_GB": "English", # 英国
|
||||
"en_AU": "English", # 澳大利亚
|
||||
"en_CA": "English", # 加拿大
|
||||
# ===== 日语 =====
|
||||
"ja_JP": "日本語", # 日本
|
||||
# ===== 俄语 =====
|
||||
"ru_RU": "Русский", # 俄罗斯
|
||||
"ru": "Русский",
|
||||
# ===== 葡萄牙语 =====
|
||||
"pt": "Português",
|
||||
"pt_BR": "Português", # 巴西
|
||||
"pt_PT": "Português", # 葡萄牙
|
||||
# ===== 泰米尔语 =====
|
||||
"ta": "தமிழ்",
|
||||
"ta_TA": "தமிழ்",
|
||||
}
|
||||
|
||||
""" 暂未启用的语言
|
||||
# ===== 韩语 =====
|
||||
"ko_KR": "한국어", # 韩国
|
||||
# ===== 法语 =====
|
||||
"fr_FR": "Français", # 法国
|
||||
"fr": "Français",
|
||||
"fr_CA": "Français", # 加拿大(魁北克)
|
||||
"fr_BE": "Français", # 比利时
|
||||
# ===== 意大利语 =====
|
||||
"it_IT": "Italiano",
|
||||
# ===== 挪威语 =====
|
||||
"nb_NO": "norsk",
|
||||
# ===== 德语 =====
|
||||
"de_DE": "Deutsch",
|
||||
"de": "Deutsch",
|
||||
"de_AT": "Deutsch",
|
||||
"de_CH": "Deutsch",
|
||||
# ===== 西班牙语 Spanish =====
|
||||
"es_ES": "Español",
|
||||
"es_MX": "Español",
|
||||
"""
|
||||
|
||||
|
||||
class _I18n:
|
||||
def init(self, qtApp):
|
||||
translator = QTranslator()
|
||||
qtApp.translators = [translator]
|
||||
|
||||
self.langCode = ""
|
||||
self.langDict = {}
|
||||
# 获取信息
|
||||
self._getLangPath()
|
||||
text, path = self.langDict[self.langCode]
|
||||
setLangCode(self.langCode) # 设置插件翻译
|
||||
if not path:
|
||||
logger.debug("使用默认文本,未加载翻译。")
|
||||
return
|
||||
if not translator.load(path):
|
||||
msg = f"无法加载UI语言!\n[Error] Unable to load UI language: {path}"
|
||||
logger.warning(msg)
|
||||
os.MessageBox(msg, type_="warning")
|
||||
return
|
||||
if not qtApp.installTranslator(translator): # 安装翻译器
|
||||
msg = f"无法加载翻译模块!\n[Error] Unable to installTranslator: {path}"
|
||||
logger.warning(msg)
|
||||
os.MessageBox(msg, type_="warning")
|
||||
return
|
||||
logger.info(f"i18n file loaded successfully. {self.langCode} - {text}")
|
||||
|
||||
# 切换语言
|
||||
def setLanguage(self, code):
|
||||
if code in self.langDict:
|
||||
self.langCode = code
|
||||
pre_configs.setValue("i18n", code) # 写入预配置项
|
||||
return True
|
||||
return False
|
||||
|
||||
# 获取语言参数
|
||||
def getInfos(self):
|
||||
return [self.langCode, self.langDict]
|
||||
|
||||
# 获取当前翻译文件路径,如果没有配置文件则初始化
|
||||
def _getLangPath(self):
|
||||
self.langDict = {}
|
||||
self.langCode = ""
|
||||
# 搜索本地翻译文件
|
||||
for file in os.listdir(I18nDir):
|
||||
if file.endswith(".qm"):
|
||||
code = os.path.splitext(file)[0]
|
||||
path = os.path.join(I18nDir, file)
|
||||
text = LanguageCodes.get(code, code)
|
||||
self.langDict[code] = [text, path]
|
||||
if DefaultLang not in self.langDict:
|
||||
text = LanguageCodes[DefaultLang]
|
||||
self.langDict[DefaultLang] = [text, ""]
|
||||
# 加载预配置项
|
||||
code = pre_configs.getValue("i18n")
|
||||
if code in self.langDict:
|
||||
self.langCode = code
|
||||
# 未能加载,则初始化预配置
|
||||
if not self.langCode:
|
||||
import locale
|
||||
|
||||
# 取得当前系统语言
|
||||
code, encoding = locale.getdefaultlocale()
|
||||
# 映射首位代号
|
||||
if code in LanguageCodes:
|
||||
langStr = LanguageCodes[code]
|
||||
for c, l in LanguageCodes.items():
|
||||
if l == langStr:
|
||||
code = c
|
||||
break
|
||||
# 尝试写入配置
|
||||
if not self.setLanguage(code):
|
||||
# 写入配置失败,则使用默认语言
|
||||
self.setLanguage(DefaultLang)
|
||||
logger.warning(
|
||||
f"The current system language is {code} and there is no corresponding i18n file. The default language used is {DefaultLang}."
|
||||
)
|
||||
|
||||
|
||||
I18n = _I18n()
|
||||
76
UmiOCR-data/py_src/utils/pre_configs.py
Normal file
76
UmiOCR-data/py_src/utils/pre_configs.py
Normal file
@@ -0,0 +1,76 @@
|
||||
# 程序的配置分为两部分,一部分是由qml引擎控制的主配置,必须启动qml才能访问。
|
||||
# 而这里是第二部分的配置项,单独存放少量关键配置,可以在未启动qml之前访问。
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
_FileName = "./.pre_settings"
|
||||
|
||||
_Configs = {
|
||||
"i18n": "", # 界面语言
|
||||
"opengl": "", # 界面OpenGL渲染类型
|
||||
"server_port": 1224, # 服务端口号
|
||||
"last_pid": -1, # 最后一次运行时的进程号
|
||||
"last_ptime": -1, # 最后一次运行时的进程创建时间
|
||||
}
|
||||
|
||||
_Errors = {} # 记录读写预配置文件的异常情况
|
||||
|
||||
|
||||
def getValue(key):
|
||||
if key in _Configs:
|
||||
return _Configs[key]
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
|
||||
def setValue(key, value):
|
||||
if key in _Configs:
|
||||
_Configs[key] = value
|
||||
writeConfigs()
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
|
||||
def writeConfigs():
|
||||
global _Errors
|
||||
try:
|
||||
with open(_FileName, "w", encoding="utf-8") as file:
|
||||
json.dump(_Configs, file, ensure_ascii=False, indent=4)
|
||||
except PermissionError:
|
||||
_Errors[
|
||||
"Write PermissionError"
|
||||
] = "权限不足,无法写入配置文件。\nInsufficient permissions, unable to write to the configuration file."
|
||||
except Exception as e:
|
||||
_Errors[
|
||||
"Write Error"
|
||||
] = f"无法写入配置文件。\nUnable to write to the configuration file: {e}"
|
||||
|
||||
|
||||
def readConfigs():
|
||||
global _Errors
|
||||
if not os.path.exists(_FileName):
|
||||
return
|
||||
try:
|
||||
with open(_FileName, "r") as file:
|
||||
data = json.load(file)
|
||||
for key in _Configs:
|
||||
_Configs[key] = data[key]
|
||||
except PermissionError:
|
||||
_Errors[
|
||||
"Write PermissionError"
|
||||
] = "权限不足,无法读取配置文件。\nInsufficient permissions, unable to read to the configuration file."
|
||||
except Exception as e:
|
||||
_Errors[
|
||||
"Write Error"
|
||||
] = f"无法读取配置文件。\nUnable to read to the configuration file: {e}"
|
||||
|
||||
|
||||
# 返回异常情况字符串
|
||||
def getErrorStr():
|
||||
global _Errors
|
||||
err = ""
|
||||
if _Errors:
|
||||
for e in _Errors.values():
|
||||
err += e + "\n"
|
||||
return err
|
||||
33
UmiOCR-data/py_src/utils/theme_connector.py
Normal file
33
UmiOCR-data/py_src/utils/theme_connector.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# =========================================
|
||||
# =============== 主题连接器 ===============
|
||||
# =========================================
|
||||
|
||||
from PySide2.QtCore import QObject, Slot
|
||||
|
||||
from umi_log import logger
|
||||
|
||||
ThemePath = "themes.json"
|
||||
|
||||
|
||||
class ThemeConnector(QObject):
|
||||
# 读取主题
|
||||
@Slot(result=str)
|
||||
def loadThemeStr(self):
|
||||
try:
|
||||
with open(ThemePath, "r", encoding="utf-8") as f:
|
||||
r = f.read()
|
||||
return r
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except Exception:
|
||||
logger.warning("读取主题文件失败。", exc_info=True)
|
||||
return ""
|
||||
|
||||
# 保存主题
|
||||
@Slot(str)
|
||||
def saveThemeStr(self, tstr):
|
||||
try:
|
||||
with open(ThemePath, "w", encoding="utf-8") as f:
|
||||
f.write(tstr)
|
||||
except Exception:
|
||||
logger.warning("写入主题文件失败。", exc_info=True)
|
||||
42
UmiOCR-data/py_src/utils/thread_pool.py
Normal file
42
UmiOCR-data/py_src/utils/thread_pool.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# =====================================================
|
||||
# =============== 全局线程池 异步任务接口 ===============
|
||||
# =====================================================
|
||||
|
||||
from PySide2.QtCore import QThreadPool, QRunnable
|
||||
|
||||
from umi_log import logger
|
||||
|
||||
# 全局线程池
|
||||
GlobalThreadPool = QThreadPool.globalInstance()
|
||||
|
||||
|
||||
# 异步类
|
||||
class Runnable(QRunnable):
|
||||
def __init__(self, taskFunc, *args, **kwargs):
|
||||
super().__init__()
|
||||
self._taskFunc = taskFunc
|
||||
self._args = args
|
||||
self._kwargs = kwargs
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self._taskFunc(*self._args, **self._kwargs)
|
||||
except Exception:
|
||||
logger.error("异步运行发生错误。", exc_info=True, stack_info=True)
|
||||
|
||||
|
||||
# 启动异步类
|
||||
def threadPoolStart(runnable: QRunnable):
|
||||
# 检查线程池是否满,并扩充
|
||||
activeThreadCount = GlobalThreadPool.activeThreadCount()
|
||||
if activeThreadCount >= GlobalThreadPool.maxThreadCount():
|
||||
logger.debug(f"线程池已满 {activeThreadCount} !自动扩充+1。")
|
||||
GlobalThreadPool.setMaxThreadCount(activeThreadCount + 1)
|
||||
GlobalThreadPool.start(runnable)
|
||||
|
||||
|
||||
# 快捷接口:异步运行函数,返回异步类的对象
|
||||
def threadRun(taskFunc, *args, **kwargs):
|
||||
runnable = Runnable(taskFunc, *args, **kwargs)
|
||||
threadPoolStart(runnable)
|
||||
return runnable
|
||||
104
UmiOCR-data/py_src/utils/utils.py
Normal file
104
UmiOCR-data/py_src/utils/utils.py
Normal file
@@ -0,0 +1,104 @@
|
||||
# =======================================
|
||||
# =============== 通用工具 ===============
|
||||
# =======================================
|
||||
|
||||
import re
|
||||
import os
|
||||
from PySide2.QtGui import QClipboard
|
||||
from PySide2.QtCore import QFileInfo
|
||||
from PySide2.QtQml import QJSValue
|
||||
from urllib.parse import unquote # 路径解码
|
||||
|
||||
from umi_log import logger
|
||||
|
||||
Clipboard = QClipboard() # 剪贴板
|
||||
|
||||
|
||||
# 传入文件名,检测是否含非法字符。没问题返回True
|
||||
def allowedFileName(fn):
|
||||
pattern = r'[\\/:*?"<>|]'
|
||||
if re.search(pattern, fn):
|
||||
return False # 转布尔值
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
# 复制文本到剪贴板
|
||||
def copyText(text):
|
||||
Clipboard.setText(text)
|
||||
|
||||
|
||||
# QUrl列表 转 String列表
|
||||
def QUrl2String(urls):
|
||||
resList = []
|
||||
for url in urls:
|
||||
if url.isLocalFile():
|
||||
u = unquote(url.toLocalFile()) # 解码路径
|
||||
if QFileInfo(u).exists(): # 检查路径是否真的存在
|
||||
resList.append(u)
|
||||
return resList
|
||||
|
||||
|
||||
# 初始化配置项字典数值,等同于 Configs.qml 的 function initConfigDict
|
||||
# 主要是为了补充type和default
|
||||
def initConfigDict(dic):
|
||||
toDict = {}
|
||||
|
||||
def handleConfigItem(config, key): # 处理一个配置项
|
||||
# 类型:指定type
|
||||
if not config["type"] == "":
|
||||
if config["type"] == "file": # 文件选择
|
||||
config["default"] = "" if not config["default"] is None else None
|
||||
elif config["type"] == "var" and config["default"] is None: # 任意类型
|
||||
config["default"] = ""
|
||||
# 类型:省略type
|
||||
else:
|
||||
if isinstance(config["default"], bool): # 布尔
|
||||
config["type"] = "boolean"
|
||||
elif "optionsList" in config: # 枚举
|
||||
config["type"] = "enum"
|
||||
if len(config["optionsList"]) == 0:
|
||||
logger.error(f"处理配置项异常:{key}枚举列表为空。")
|
||||
return
|
||||
if config["default"] is None:
|
||||
config["default"] = config["optionsList"][0][0]
|
||||
elif isinstance(config["default"], str): # 文本
|
||||
config["type"] = "text"
|
||||
elif isinstance(config["default"], (int, float)): # 数字
|
||||
config["type"] = "number"
|
||||
elif "btnsList" in config: # 按钮组
|
||||
config["type"] = "buttons"
|
||||
return
|
||||
else:
|
||||
logger.error(f"未知类型的配置项:{key}")
|
||||
return
|
||||
|
||||
def handleConfigGroup(group, prefix=""): # 处理一个配置组
|
||||
for key in group:
|
||||
config = group[key]
|
||||
if not isinstance(config, dict):
|
||||
continue
|
||||
# 补充空白参数
|
||||
if "type" not in config: # 类型
|
||||
config["type"] = ""
|
||||
if "default" not in config: # 默认值
|
||||
config["default"] = None
|
||||
if "advanced" not in config: # 是否为高级选项
|
||||
config["advanced"] = False
|
||||
# 记录完整key
|
||||
fullKey = prefix + key
|
||||
if config["type"] == "group": # 若是配置项组,递归遍历
|
||||
handleConfigGroup(config, fullKey + ".") # 前缀加深一层
|
||||
else: # 若是配置项
|
||||
toDict[fullKey] = config
|
||||
handleConfigItem(config, fullKey)
|
||||
|
||||
handleConfigGroup(dic)
|
||||
return toDict
|
||||
|
||||
|
||||
# 整理 argd 参数字典,将 float 恢复 int 类型,如 12.0 → 12
|
||||
def argdIntConvert(argd):
|
||||
for k, v in argd.items():
|
||||
if isinstance(v, float) and v.is_integer():
|
||||
argd[k] = int(v)
|
||||
63
UmiOCR-data/py_src/utils/utils_connector.py
Normal file
63
UmiOCR-data/py_src/utils/utils_connector.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# 通用工具连接器
|
||||
|
||||
from typing import List
|
||||
from PySide2.QtCore import QObject, Slot
|
||||
|
||||
from . import utils
|
||||
from . import file_finder # 文件搜索器
|
||||
from ..platform import Platform # 跨平台
|
||||
from .thread_pool import threadRun # 异步执行函数
|
||||
|
||||
|
||||
class UtilsConnector(QObject):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# 将文本写入剪贴板
|
||||
@Slot(str)
|
||||
def copyText(self, text):
|
||||
utils.copyText(text)
|
||||
|
||||
# 用系统应用打开文件或目录
|
||||
@Slot(str)
|
||||
def startfile(self, path):
|
||||
Platform.startfile(path)
|
||||
|
||||
# 硬件控制
|
||||
@Slot(str)
|
||||
def hardwareCtrl(self, key):
|
||||
if key == "shutdown": # 关机
|
||||
Platform.HardwareCtrl.shutdown()
|
||||
elif key == "hibernate": # 休眠
|
||||
Platform.HardwareCtrl.hibernate()
|
||||
|
||||
# 同步搜索文件,返回合法的文件路径列表
|
||||
@Slot("QVariant", bool, str, result="QVariant")
|
||||
def findFiles(self, paths, sufType, isRecurrence):
|
||||
return file_finder.findFiles(paths, sufType, isRecurrence)
|
||||
|
||||
# 异步搜索文件
|
||||
@Slot("QVariant", str, bool, str, str, float)
|
||||
def asynFindFiles(
|
||||
self,
|
||||
paths: List, # 初始路径列表
|
||||
sufType: str, # 后缀类型,FileSuf的key
|
||||
isRecurrence: bool, # 若为True,则递归搜索
|
||||
completeKey: str, # 全部完成后的事件key。向事件传入合法路径列表。
|
||||
updateKey: str, # 加载中刷新进度的key,不填则无。向事件传入 (已完成的路径数量, 最近一条路径)
|
||||
updateTime: float, # 刷新进度的间距
|
||||
):
|
||||
threadRun(
|
||||
file_finder.asynFindFiles,
|
||||
paths,
|
||||
sufType,
|
||||
isRecurrence,
|
||||
completeKey,
|
||||
updateKey,
|
||||
updateTime,
|
||||
)
|
||||
|
||||
# QUrl列表 转 String列表
|
||||
@Slot("QVariant", result="QVariant")
|
||||
def QUrl2String(self, fileUrls):
|
||||
return utils.QUrl2String(fileUrls)
|
||||
Reference in New Issue
Block a user