cmd_client.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. # ============================================
  2. # =============== 命令行-客户端 ===============
  3. # ============================================
  4. import os
  5. import sys
  6. import time
  7. import psutil
  8. from umi_log import logger
  9. from ..utils import pre_configs
  10. from ..platform import Platform
  11. # 获取进程的创建时间
  12. def getPidTime(pid):
  13. try:
  14. return str(psutil.Process(pid).create_time())
  15. except psutil.NoSuchProcess:
  16. logger.warning(
  17. "psutil.pid_exists(pid) 存在,但 Process 无法生成对象",
  18. exc_info=True,
  19. stack_info=True,
  20. )
  21. return ""
  22. except Exception:
  23. logger.error("psutil.Process(pid) error", exc_info=True, stack_info=True)
  24. return ""
  25. # 检查软件多开
  26. def _isMultiOpen():
  27. # 检查上次记录的pid和key是否还在运行
  28. recordPID = pre_configs.getValue("last_pid")
  29. recordPTime = pre_configs.getValue("last_ptime")
  30. if psutil.pid_exists(recordPID): # 上次记录的pid如今存在
  31. processTime = getPidTime(recordPID)
  32. if recordPTime == processTime: # 当前该进程启动时间与记录的相同,则为多开
  33. return True
  34. return False
  35. # 输出
  36. def _output(argv, argument, mode, text):
  37. if argument not in argv:
  38. return
  39. path = ""
  40. # 提取路径参数
  41. try:
  42. i = argv.index(argument)
  43. path = argv[i + 1]
  44. del argv[i : i + 2]
  45. except Exception as e:
  46. # logger 输出到 stderr , print 输出到 stdout
  47. logger.error(f"argument {argument} cannot be resolved.", exc_info=True)
  48. print(f"[Error] argument {argument} cannot be resolved. \n{e}")
  49. return
  50. # 相对路径转绝对路径
  51. if not os.path.isabs(path):
  52. # 获取当前工作目录的上一级目录
  53. current_dir = os.getcwd()
  54. parent_dir = os.path.abspath(os.path.join(current_dir, os.pardir))
  55. # 将 path 转为绝对路径,且以上一级目录为基准
  56. path = os.path.abspath(os.path.join(parent_dir, path))
  57. try:
  58. with open(path, mode, encoding="utf-8") as f:
  59. f.write(text)
  60. print(f"\nSuccess output to file: {path}")
  61. except Exception as e:
  62. logger.error(f"failed to write file {path} .", exc_info=True)
  63. print(f"[Error] failed to write file {path} : \n{e}")
  64. return
  65. # 复制文本到剪贴板,不依赖第三方库
  66. def _clip(text):
  67. import subprocess
  68. import platform
  69. import tempfile
  70. os_type = platform.system()
  71. try:
  72. if os_type == "Windows":
  73. # 创建一个临时文件,并立即关闭它,以便其他进程可以访问
  74. with tempfile.NamedTemporaryFile(
  75. delete=False, mode="w+", newline="\n"
  76. ) as temp_file:
  77. temp_file.write(text)
  78. temp_file_name = temp_file.name
  79. temp_file.close()
  80. try:
  81. subprocess.run(f"clip < {temp_file_name}", check=True, shell=True)
  82. finally:
  83. os.unlink(temp_file_name) # 删除临时文件
  84. print("\nSuccess copy to clipboard.")
  85. else:
  86. print(f"[Error] clip unsupported OS: {os_type}")
  87. except Exception as e:
  88. logger.error("failed to copy to clipboard.", exc_info=True)
  89. print(f"[Error] failed to copy to clipboard: {e}")
  90. # 跨进程发送指令
  91. def _sendCmd(argv):
  92. port = pre_configs.getValue("server_port") # 记录的端口号
  93. url = f"http://127.0.0.1:{port}/argv" # argv,指令列表接口
  94. errStr = f"Umi-OCR 已在运行,HTTP跨进程传输指令失败。\n[Error] Umi-OCR is already running, HTTP cross process transmission instruction failed.\n{url}"
  95. import urllib.request
  96. import json
  97. # 向后台工作进程发送指令
  98. res = ""
  99. try:
  100. data = json.dumps(argv, ensure_ascii=True).encode("utf-8")
  101. req = urllib.request.Request(
  102. url, data=data, headers={"Content-Type": "application/json"}
  103. )
  104. # response = urllib.request.urlopen(req)
  105. # 创建一个不使用代理的 opener ,发送请求
  106. opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
  107. response = opener.open(req)
  108. if response.status == 200:
  109. res = response.read().decode("utf-8")
  110. else:
  111. res = f"{errStr}\nstatus_code: {response.status}"
  112. except Exception as e:
  113. res = f"{errStr}\nerror: {e}"
  114. # 输出
  115. print(res)
  116. _output(argv, "-->", "w", res)
  117. _output(argv, "--output", "w", res)
  118. _output(argv, "-->>", "a", res)
  119. _output(argv, "--output_append", "a", res)
  120. if "--clip" in argv:
  121. _clip(res)
  122. # 启动新进程,并发送指令
  123. def _newSend(argv):
  124. from umi_about import UmiAbout # 项目信息
  125. appPath = UmiAbout["app"]["path"]
  126. if not appPath:
  127. msg = "未找到 Umi-OCR 入口路径,无法启动新进程。请手动启动 Umi-OCR 后发送指令。\nUmi-OCR path not found, unable to start a new process."
  128. os.MessageBox(msg)
  129. return
  130. # 启动进程,传入强制参数,避免递归无限启动进程
  131. Platform.runNewProcess(appPath, " --force")
  132. # 等待并检查 服务进程初始化完毕
  133. for i in range(60): # 检测轮次
  134. time.sleep(0.5) # 每次等待时间
  135. pre_configs.readConfigs() # 重新读取预配置
  136. if _isMultiOpen(): # 检测新进程是否启动
  137. _sendCmd(argv) # 发送指令
  138. return
  139. print(
  140. "服务进程初始化失败,等待时间超时。\n[Error] The service process initialization failed and the waiting time timed out."
  141. )
  142. # 初始化命令行
  143. def initCmd():
  144. argv = sys.argv[1:]
  145. force = False
  146. if "--force" in argv:
  147. argv.remove("--force")
  148. force = True
  149. # 检查,发现软件多开,则向已在运行的进程发送初始指令
  150. if _isMultiOpen():
  151. _sendCmd(argv)
  152. return False
  153. # 未多开,则启动进程
  154. else:
  155. # 无参数或强制启动,则正常运行本进程,刷新pid和ptime
  156. if not argv or force:
  157. nowPid = os.getpid()
  158. nowPTime = getPidTime(nowPid)
  159. pre_configs.setValue("last_pid", nowPid)
  160. pre_configs.setValue("last_ptime", nowPTime)
  161. return True
  162. else: # 有参数,则启动新进程并发送参数
  163. _newSend(argv)
  164. return False