ryry-cli 3.4__tar.gz → 4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ryry_cli-3.4 → ryry_cli-4.0}/PKG-INFO +3 -2
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry/constant.py +2 -2
- ryry_cli-4.0/ryry/daemon_base.py +131 -0
- ryry_cli-4.0/ryry/daemon_manager.py +395 -0
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry/main.py +72 -6
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry/ryry_server_socket.py +283 -279
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry/ryry_service.py +47 -9
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry/ryry_webapi.py +18 -20
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry/ryry_widget.py +64 -3
- ryry_cli-4.0/ryry/script_template/daemon.py +59 -0
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry/script_template/main.py +1 -1
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry/server_func.py +4 -3
- ryry_cli-4.0/ryry/shared_memory.py +756 -0
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry/store.py +13 -15
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry/task.py +89 -56
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry/taskUtils.py +7 -7
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry/upload.py +183 -32
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry/utils.py +19 -5
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry_cli.egg-info/PKG-INFO +3 -2
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry_cli.egg-info/SOURCES.txt +4 -0
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry_cli.egg-info/requires.txt +1 -0
- {ryry_cli-3.4 → ryry_cli-4.0}/setup.py +3 -2
- {ryry_cli-3.4 → ryry_cli-4.0}/LICENSE +0 -0
- {ryry_cli-3.4 → ryry_cli-4.0}/README.md +0 -0
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry/__init__.py +0 -0
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry/script_template/__init__.py +0 -0
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry/script_template/run.py +0 -0
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry_cli.egg-info/dependency_links.txt +0 -0
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry_cli.egg-info/entry_points.txt +0 -0
- {ryry_cli-3.4 → ryry_cli-4.0}/ryry_cli.egg-info/top_level.txt +0 -0
- {ryry_cli-3.4 → ryry_cli-4.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ryry-cli
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0
|
|
4
4
|
Summary: ryry tools
|
|
5
5
|
Home-page: https://github.com/dalipenMedia
|
|
6
6
|
Author: dalipen
|
|
@@ -8,7 +8,7 @@ Author-email: dalipen01@gmail.com
|
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: License :: OSI Approved :: MIT License
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
|
-
Requires-Python: >=3.
|
|
11
|
+
Requires-Python: >=3.5
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: requests
|
|
@@ -22,6 +22,7 @@ Requires-Dist: fake_useragent
|
|
|
22
22
|
Requires-Dist: gputil
|
|
23
23
|
Requires-Dist: urlparser
|
|
24
24
|
Requires-Dist: urllib3
|
|
25
|
+
Requires-Dist: portalocker
|
|
25
26
|
|
|
26
27
|
ryry Python Tool
|
|
27
28
|
===============================================
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from threading import Event
|
|
6
|
+
|
|
7
|
+
DEFAULT_ACCEPT_TASKS = False # 默认是否接受短链任务
|
|
8
|
+
|
|
9
|
+
class DaemonBase:
|
|
10
|
+
def __init__(self, widget_id: str):
|
|
11
|
+
self.widget_id = widget_id
|
|
12
|
+
self.running = True
|
|
13
|
+
self.stop_event = Event()
|
|
14
|
+
self.accept_tasks = self.default_accept_tasks()
|
|
15
|
+
self.widget_name = "???"
|
|
16
|
+
# 获取子类文件(即实际widget的daemon.py)所在目录
|
|
17
|
+
self._script_path = os.path.abspath(sys.modules[self.__class__.__module__].__file__)
|
|
18
|
+
self.base_path = sys.argv[2] if len(sys.argv) > 2 else os.path.expanduser("~/.ryry")
|
|
19
|
+
config_path = os.path.join(os.path.dirname(self._script_path), "config.json")
|
|
20
|
+
if os.path.exists(config_path):
|
|
21
|
+
try:
|
|
22
|
+
with open(config_path, 'r', encoding='utf-8') as f:
|
|
23
|
+
config = json.load(f)
|
|
24
|
+
self.timeout = config.get("timeout", 600)
|
|
25
|
+
self.widget_name = config.get("name", self.widget_name)
|
|
26
|
+
except:
|
|
27
|
+
self.timeout = 600
|
|
28
|
+
else:
|
|
29
|
+
self.timeout = 600
|
|
30
|
+
|
|
31
|
+
def default_accept_tasks(self):
|
|
32
|
+
return DEFAULT_ACCEPT_TASKS
|
|
33
|
+
|
|
34
|
+
def initialize(self):
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
def process_task(self, task_data, timeout=None):
|
|
38
|
+
raise NotImplementedError("process_task必须由子类实现")
|
|
39
|
+
|
|
40
|
+
def on_stop(self):
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
def health_check(self):
|
|
44
|
+
return {
|
|
45
|
+
"healthy": True,
|
|
46
|
+
"accept_tasks": self.accept_tasks,
|
|
47
|
+
"timestamp": time.time()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
def loop_function(self):
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
def _send_ready_signal(self):
|
|
54
|
+
try:
|
|
55
|
+
ready_file = os.path.join(self.base_path, f"daemon_ready_{self.widget_id}.json")
|
|
56
|
+
with open(ready_file, 'w', encoding='utf-8') as f:
|
|
57
|
+
json.dump({"accept_tasks": self.accept_tasks}, f)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
print(f"Failed to send ready signal: {e}", file=sys.stderr)
|
|
60
|
+
|
|
61
|
+
def _send_response(self, response):
|
|
62
|
+
try:
|
|
63
|
+
result_file = os.path.join(self.base_path, f"daemon_result_{self.widget_id}.json")
|
|
64
|
+
with open(result_file, 'w', encoding='utf-8') as f:
|
|
65
|
+
json.dump(response, f)
|
|
66
|
+
except Exception as e:
|
|
67
|
+
print(f"Failed to send response: {e}", file=sys.stderr)
|
|
68
|
+
|
|
69
|
+
def _process_command(self, command):
|
|
70
|
+
cmd_type = command.get("type")
|
|
71
|
+
if cmd_type == "task":
|
|
72
|
+
task_data = command.get("data", {})
|
|
73
|
+
timeout = command.get("timeout", self.timeout)
|
|
74
|
+
try:
|
|
75
|
+
result = self.process_task(task_data, timeout)
|
|
76
|
+
self._send_response({
|
|
77
|
+
"type": "task_result",
|
|
78
|
+
"task_id": command.get("task_id"),
|
|
79
|
+
"success": True,
|
|
80
|
+
"data": result
|
|
81
|
+
})
|
|
82
|
+
except Exception as e:
|
|
83
|
+
self._send_response({
|
|
84
|
+
"type": "task_result",
|
|
85
|
+
"task_id": command.get("task_id"),
|
|
86
|
+
"success": False,
|
|
87
|
+
"error": str(e),
|
|
88
|
+
"data": {"result": [], "status": 1, "message": str(e)}
|
|
89
|
+
})
|
|
90
|
+
elif cmd_type == "health":
|
|
91
|
+
health = self.health_check()
|
|
92
|
+
self._send_response({
|
|
93
|
+
"type": "health_result",
|
|
94
|
+
"data": health
|
|
95
|
+
})
|
|
96
|
+
elif cmd_type == "stop":
|
|
97
|
+
print(f"【{self.widget_name}后台进程】Received stop command", file=sys.stderr)
|
|
98
|
+
self.running = False
|
|
99
|
+
self.stop_event.set()
|
|
100
|
+
self._send_response({
|
|
101
|
+
"type": "stop_result",
|
|
102
|
+
"success": True
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
def run(self):
|
|
106
|
+
try:
|
|
107
|
+
self.initialize()
|
|
108
|
+
self._send_ready_signal()
|
|
109
|
+
cmd_file = os.path.join(self.base_path, f"daemon_cmd_{self.widget_id}.json")
|
|
110
|
+
while self.running:
|
|
111
|
+
try:
|
|
112
|
+
if os.path.exists(cmd_file):
|
|
113
|
+
try:
|
|
114
|
+
with open(cmd_file, 'r', encoding='utf-8') as f:
|
|
115
|
+
command = json.load(f)
|
|
116
|
+
os.remove(cmd_file)
|
|
117
|
+
self._process_command(command)
|
|
118
|
+
except Exception as e:
|
|
119
|
+
print(f"Error processing command: {e}", file=sys.stderr)
|
|
120
|
+
if self.stop_event.is_set():
|
|
121
|
+
break
|
|
122
|
+
self.loop_function()
|
|
123
|
+
time.sleep(1)
|
|
124
|
+
except KeyboardInterrupt:
|
|
125
|
+
break
|
|
126
|
+
except Exception as e:
|
|
127
|
+
print(f"Error in main loop: {e}", file=sys.stderr)
|
|
128
|
+
time.sleep(1)
|
|
129
|
+
finally:
|
|
130
|
+
self.on_stop()
|
|
131
|
+
print(f"【{self.widget_name}】已终止", file=sys.stderr)
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
import signal
|
|
6
|
+
import subprocess
|
|
7
|
+
import threading
|
|
8
|
+
from threading import Thread, Lock
|
|
9
|
+
from typing import Dict, Optional, Tuple
|
|
10
|
+
import queue
|
|
11
|
+
import calendar
|
|
12
|
+
import uuid
|
|
13
|
+
|
|
14
|
+
from ryry import constant, store, taskUtils, utils
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DaemonManager:
|
|
18
|
+
"""基于文件的常驻进程管理器"""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.daemon_config_file = os.path.join(constant.base_path, "daemon_processes.json")
|
|
22
|
+
self.lock = Lock()
|
|
23
|
+
self._ensure_config_file()
|
|
24
|
+
|
|
25
|
+
def _ensure_config_file(self):
|
|
26
|
+
"""确保配置文件存在"""
|
|
27
|
+
if not os.path.exists(self.daemon_config_file):
|
|
28
|
+
with open(self.daemon_config_file, 'w', encoding='utf-8') as f:
|
|
29
|
+
json.dump({}, f)
|
|
30
|
+
|
|
31
|
+
def _read_daemon_config(self) -> dict:
|
|
32
|
+
"""读取daemon配置"""
|
|
33
|
+
try:
|
|
34
|
+
with open(self.daemon_config_file, 'r', encoding='utf-8') as f:
|
|
35
|
+
return json.load(f)
|
|
36
|
+
except:
|
|
37
|
+
return {}
|
|
38
|
+
|
|
39
|
+
def _write_daemon_config(self, config: dict):
|
|
40
|
+
"""写入daemon配置"""
|
|
41
|
+
try:
|
|
42
|
+
with open(self.daemon_config_file, 'w', encoding='utf-8') as f:
|
|
43
|
+
json.dump(config, f, indent=2)
|
|
44
|
+
except Exception as e:
|
|
45
|
+
taskUtils.taskPrint(None, f"Write daemon config failed: {e}")
|
|
46
|
+
|
|
47
|
+
def _is_process_alive(self, pid: int) -> bool:
|
|
48
|
+
"""检查进程是否还活着"""
|
|
49
|
+
try:
|
|
50
|
+
if pid <= 0:
|
|
51
|
+
return False
|
|
52
|
+
os.kill(pid, 0)
|
|
53
|
+
return True
|
|
54
|
+
except (OSError, ProcessLookupError):
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
def _send_command_to_process(self, widget_id: str, command: dict, timeout: int = 30) -> Optional[dict]:
|
|
58
|
+
"""发送命令到进程"""
|
|
59
|
+
try:
|
|
60
|
+
config = self._read_daemon_config()
|
|
61
|
+
daemon_info = config.get(widget_id, {})
|
|
62
|
+
|
|
63
|
+
if not daemon_info.get("running", False):
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
# 这里可以通过管道、socket等方式与进程通信
|
|
67
|
+
# 为了简化,我们假设进程会创建一个命令文件
|
|
68
|
+
cmd_file = os.path.join(constant.base_path, f"daemon_cmd_{widget_id}.json")
|
|
69
|
+
result_file = os.path.join(constant.base_path, f"daemon_result_{widget_id}.json")
|
|
70
|
+
|
|
71
|
+
# 清理旧的结果文件
|
|
72
|
+
if os.path.exists(result_file):
|
|
73
|
+
os.remove(result_file)
|
|
74
|
+
|
|
75
|
+
# 写入命令
|
|
76
|
+
with open(cmd_file, 'w', encoding='utf-8') as f:
|
|
77
|
+
json.dump(command, f)
|
|
78
|
+
|
|
79
|
+
# 等待结果
|
|
80
|
+
start_time = time.time()
|
|
81
|
+
while time.time() - start_time < timeout: # 使用传入的timeout
|
|
82
|
+
if os.path.exists(result_file):
|
|
83
|
+
try:
|
|
84
|
+
with open(result_file, 'r', encoding='utf-8') as f:
|
|
85
|
+
response = json.load(f)
|
|
86
|
+
os.remove(result_file)
|
|
87
|
+
return response
|
|
88
|
+
except:
|
|
89
|
+
pass
|
|
90
|
+
time.sleep(1)
|
|
91
|
+
|
|
92
|
+
return None
|
|
93
|
+
except Exception as e:
|
|
94
|
+
taskUtils.taskPrint(None, f"Send command to daemon {widget_id} failed: {e}")
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
def has_daemon_file(self, widget_id: str) -> bool:
|
|
98
|
+
"""检查widget是否有daemon.py文件"""
|
|
99
|
+
try:
|
|
100
|
+
widget_path = self._get_widget_path(widget_id)
|
|
101
|
+
if not widget_path:
|
|
102
|
+
return False
|
|
103
|
+
daemon_file = os.path.join(os.path.dirname(widget_path), "daemon.py")
|
|
104
|
+
return os.path.exists(daemon_file)
|
|
105
|
+
except:
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
def is_daemon_enabled(self, widget_id: str) -> bool:
|
|
109
|
+
"""检查widget是否启用daemon功能"""
|
|
110
|
+
try:
|
|
111
|
+
widget_config = self._get_widget_config(widget_id)
|
|
112
|
+
if not widget_config:
|
|
113
|
+
return False
|
|
114
|
+
return widget_config.get("daemon_enabled", False)
|
|
115
|
+
except:
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
def should_start_daemon(self, widget_id: str) -> bool:
|
|
119
|
+
"""判断是否应该启动daemon"""
|
|
120
|
+
return self.has_daemon_file(widget_id) and self.is_daemon_enabled(widget_id)
|
|
121
|
+
|
|
122
|
+
def _get_widget_config(self, widget_id: str) -> Optional[dict]:
|
|
123
|
+
"""获取widget配置"""
|
|
124
|
+
try:
|
|
125
|
+
widget_path = self._get_widget_path(widget_id)
|
|
126
|
+
if not widget_path:
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
config_path = os.path.join(os.path.dirname(widget_path), "config.json")
|
|
130
|
+
if os.path.exists(config_path):
|
|
131
|
+
with open(config_path, 'r', encoding='utf-8') as f:
|
|
132
|
+
return json.load(f)
|
|
133
|
+
except:
|
|
134
|
+
pass
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
def _get_widget_path(self, widget_id: str) -> Optional[str]:
|
|
138
|
+
"""获取widget主文件路径"""
|
|
139
|
+
widget_map = store.widgetMap()
|
|
140
|
+
if widget_id not in widget_map:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
widget_info = widget_map[widget_id]
|
|
144
|
+
if isinstance(widget_info, dict):
|
|
145
|
+
return widget_info["path"]
|
|
146
|
+
else:
|
|
147
|
+
return widget_info
|
|
148
|
+
|
|
149
|
+
def start_daemon(self, widget_id: str, widget_info: dict = None) -> bool:
|
|
150
|
+
"""启动指定widget的常驻进程"""
|
|
151
|
+
with self.lock:
|
|
152
|
+
config = self._read_daemon_config()
|
|
153
|
+
# 检查是否已经在运行
|
|
154
|
+
if widget_id in config:
|
|
155
|
+
daemon_info = config[widget_id]
|
|
156
|
+
if daemon_info.get("running", False) and self._is_process_alive(daemon_info.get("pid", 0)):
|
|
157
|
+
return True # 已经在运行
|
|
158
|
+
# 清理无效的进程信息
|
|
159
|
+
del config[widget_id]
|
|
160
|
+
self._write_daemon_config(config)
|
|
161
|
+
if not self.should_start_daemon(widget_id):
|
|
162
|
+
return False
|
|
163
|
+
try:
|
|
164
|
+
widget_path = self._get_widget_path(widget_id)
|
|
165
|
+
if not widget_path:
|
|
166
|
+
return False
|
|
167
|
+
widget_dir = os.path.dirname(widget_path)
|
|
168
|
+
daemon_file = os.path.join(widget_dir, "daemon.py")
|
|
169
|
+
# 获取widget的timeout配置和version
|
|
170
|
+
if widget_info and isinstance(widget_info, dict):
|
|
171
|
+
timeout = widget_info.get("timeout", 600)
|
|
172
|
+
version = widget_info.get("version", "1.0")
|
|
173
|
+
else:
|
|
174
|
+
widget_config = self._get_widget_config(widget_id)
|
|
175
|
+
timeout = widget_config.get("timeout", 600) if widget_config else 600
|
|
176
|
+
version = widget_config.get("version", "1.0") if widget_config else "1.0"
|
|
177
|
+
# 启动进程
|
|
178
|
+
process = subprocess.Popen(
|
|
179
|
+
[sys.executable, daemon_file, widget_id, constant.base_path],
|
|
180
|
+
cwd=widget_dir,
|
|
181
|
+
env=os.environ.copy()
|
|
182
|
+
)
|
|
183
|
+
# 记录进程信息
|
|
184
|
+
daemon_info = {
|
|
185
|
+
"widget_id": widget_id,
|
|
186
|
+
"pid": process.pid,
|
|
187
|
+
"start_time": time.time(),
|
|
188
|
+
"running": True,
|
|
189
|
+
"ready": False,
|
|
190
|
+
"accept_tasks": False,
|
|
191
|
+
"timeout": timeout,
|
|
192
|
+
"version": version
|
|
193
|
+
}
|
|
194
|
+
config[widget_id] = daemon_info
|
|
195
|
+
self._write_daemon_config(config)
|
|
196
|
+
# 等待就绪信号(通过文件)
|
|
197
|
+
ready_file = os.path.join(constant.base_path, f"daemon_ready_{widget_id}.json")
|
|
198
|
+
start_time = time.time()
|
|
199
|
+
while time.time() - start_time < 30: # 30秒超时
|
|
200
|
+
if os.path.exists(ready_file):
|
|
201
|
+
try:
|
|
202
|
+
with open(ready_file, 'r', encoding='utf-8') as f:
|
|
203
|
+
ready_info = json.load(f)
|
|
204
|
+
os.remove(ready_file)
|
|
205
|
+
daemon_info["ready"] = True
|
|
206
|
+
daemon_info["accept_tasks"] = ready_info.get("accept_tasks", False)
|
|
207
|
+
config[widget_id] = daemon_info
|
|
208
|
+
self._write_daemon_config(config)
|
|
209
|
+
break
|
|
210
|
+
except:
|
|
211
|
+
pass
|
|
212
|
+
time.sleep(1)
|
|
213
|
+
if not daemon_info["ready"]:
|
|
214
|
+
process.terminate()
|
|
215
|
+
del config[widget_id]
|
|
216
|
+
self._write_daemon_config(config)
|
|
217
|
+
return False
|
|
218
|
+
taskUtils.taskPrint(None, f"Daemon process {widget_id} started, PID: {process.pid}, accept_tasks: {daemon_info['accept_tasks']}")
|
|
219
|
+
return True
|
|
220
|
+
except Exception as e:
|
|
221
|
+
taskUtils.taskPrint(None, f"Start daemon process {widget_id} failed: {e}")
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
def stop_daemon(self, widget_id: str) -> bool:
|
|
225
|
+
"""停止指定widget的常驻进程"""
|
|
226
|
+
with self.lock:
|
|
227
|
+
config = self._read_daemon_config()
|
|
228
|
+
|
|
229
|
+
if widget_id not in config:
|
|
230
|
+
return True # 已经停止
|
|
231
|
+
|
|
232
|
+
daemon_info = config[widget_id]
|
|
233
|
+
pid = daemon_info.get("pid", 0)
|
|
234
|
+
|
|
235
|
+
if not self._is_process_alive(pid):
|
|
236
|
+
del config[widget_id]
|
|
237
|
+
self._write_daemon_config(config)
|
|
238
|
+
return True
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
# 发送停止命令
|
|
242
|
+
response = self._send_command_to_process(widget_id, {"type": "stop"}, 600)
|
|
243
|
+
if response and response.get("type") == "stop_result":
|
|
244
|
+
# 等待进程结束
|
|
245
|
+
start_time = time.time()
|
|
246
|
+
while time.time() - start_time < 30: # 30秒超时
|
|
247
|
+
if not self._is_process_alive(pid):
|
|
248
|
+
break
|
|
249
|
+
time.sleep(1)
|
|
250
|
+
|
|
251
|
+
# 如果进程还在运行,强制终止
|
|
252
|
+
if self._is_process_alive(pid):
|
|
253
|
+
os.kill(pid, signal.SIGTERM)
|
|
254
|
+
time.sleep(2)
|
|
255
|
+
if self._is_process_alive(pid):
|
|
256
|
+
os.kill(pid, signal.SIGKILL)
|
|
257
|
+
|
|
258
|
+
del config[widget_id]
|
|
259
|
+
self._write_daemon_config(config)
|
|
260
|
+
taskUtils.taskPrint(None, f"Daemon process {widget_id} stopped")
|
|
261
|
+
return True
|
|
262
|
+
|
|
263
|
+
except Exception as e:
|
|
264
|
+
taskUtils.taskPrint(None, f"Stop daemon {widget_id} failed: {e}")
|
|
265
|
+
return False
|
|
266
|
+
|
|
267
|
+
def execute_task(self, widget_id: str, task_data: dict, timeout: int = 600) -> Tuple[bool, str, dict]:
|
|
268
|
+
"""在常驻进程中执行任务"""
|
|
269
|
+
config = self._read_daemon_config()
|
|
270
|
+
daemon_info = config.get(widget_id, {})
|
|
271
|
+
|
|
272
|
+
if not daemon_info.get("running", False):
|
|
273
|
+
return False, "Daemon process not running", {}
|
|
274
|
+
|
|
275
|
+
if not self._is_process_alive(daemon_info.get("pid", 0)):
|
|
276
|
+
# 清理无效进程
|
|
277
|
+
with self.lock:
|
|
278
|
+
config = self._read_daemon_config()
|
|
279
|
+
if widget_id in config:
|
|
280
|
+
del config[widget_id]
|
|
281
|
+
self._write_daemon_config(config)
|
|
282
|
+
return False, "Daemon process is dead", {}
|
|
283
|
+
|
|
284
|
+
if not daemon_info.get("accept_tasks", False):
|
|
285
|
+
return False, "Daemon process does not accept tasks", {}
|
|
286
|
+
|
|
287
|
+
try:
|
|
288
|
+
# 生成任务ID
|
|
289
|
+
task_id = str(uuid.uuid4())
|
|
290
|
+
|
|
291
|
+
# 发送任务到进程
|
|
292
|
+
task_request = {
|
|
293
|
+
"type": "task",
|
|
294
|
+
"task_id": task_id,
|
|
295
|
+
"data": task_data,
|
|
296
|
+
"timeout": timeout
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
response = self._send_command_to_process(widget_id, task_request, timeout)
|
|
300
|
+
if response and response.get("type") == "task_result":
|
|
301
|
+
success = response.get("success", False)
|
|
302
|
+
data = response.get("data", {})
|
|
303
|
+
error = response.get("error", "")
|
|
304
|
+
return success, error, data
|
|
305
|
+
|
|
306
|
+
return False, "No response from daemon", {}
|
|
307
|
+
|
|
308
|
+
except Exception as e:
|
|
309
|
+
return False, f"Execute task failed: {str(e)}", {}
|
|
310
|
+
|
|
311
|
+
def get_daemon_status(self, widget_id: str) -> dict:
|
|
312
|
+
"""获取常驻进程状态"""
|
|
313
|
+
config = self._read_daemon_config()
|
|
314
|
+
daemon_info = config.get(widget_id, {})
|
|
315
|
+
|
|
316
|
+
if not daemon_info:
|
|
317
|
+
return {"running": False, "ready": False, "accept_tasks": False}
|
|
318
|
+
|
|
319
|
+
running = daemon_info.get("running", False) and self._is_process_alive(daemon_info.get("pid", 0))
|
|
320
|
+
|
|
321
|
+
if not running and daemon_info.get("running", False):
|
|
322
|
+
# 清理无效进程
|
|
323
|
+
with self.lock:
|
|
324
|
+
config = self._read_daemon_config()
|
|
325
|
+
if widget_id in config:
|
|
326
|
+
del config[widget_id]
|
|
327
|
+
self._write_daemon_config(config)
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
"running": running,
|
|
331
|
+
"ready": daemon_info.get("ready", False),
|
|
332
|
+
"accept_tasks": daemon_info.get("accept_tasks", False),
|
|
333
|
+
"pid": daemon_info.get("pid", 0),
|
|
334
|
+
"start_time": daemon_info.get("start_time", 0)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
def stop_all_daemons(self) -> bool:
|
|
338
|
+
"""停止所有常驻进程"""
|
|
339
|
+
config = self._read_daemon_config()
|
|
340
|
+
widget_ids = list(config.keys())
|
|
341
|
+
|
|
342
|
+
all_stopped = True
|
|
343
|
+
for widget_id in widget_ids:
|
|
344
|
+
if not self.stop_daemon(widget_id):
|
|
345
|
+
all_stopped = False
|
|
346
|
+
|
|
347
|
+
return all_stopped
|
|
348
|
+
|
|
349
|
+
def sync_daemons_with_widget_map(self):
|
|
350
|
+
"""同步daemon状态与widgetMap,自动关闭不需要的daemon,启动需要的新daemon"""
|
|
351
|
+
config = self._read_daemon_config()
|
|
352
|
+
widget_map = store.widgetMap()
|
|
353
|
+
# 1. 关闭不需要的daemon(被移除、被屏蔽、版本不一致)
|
|
354
|
+
for widget_id in list(config.keys()):
|
|
355
|
+
daemon_info = config[widget_id]
|
|
356
|
+
widget_info = widget_map.get(widget_id)
|
|
357
|
+
need_stop = False
|
|
358
|
+
# widget已被移除
|
|
359
|
+
if widget_info is None:
|
|
360
|
+
need_stop = True
|
|
361
|
+
else:
|
|
362
|
+
# 屏蔽
|
|
363
|
+
is_block = widget_info.get("isBlock", False)
|
|
364
|
+
if is_block:
|
|
365
|
+
need_stop = True
|
|
366
|
+
# 版本号不一致(仅当daemon正在运行时才判断)
|
|
367
|
+
version = widget_info.get("version", "1.0")
|
|
368
|
+
daemon_version = daemon_info.get("version", None)
|
|
369
|
+
if daemon_info.get("running", False) and daemon_version is not None and daemon_version != version:
|
|
370
|
+
need_stop = True
|
|
371
|
+
if need_stop:
|
|
372
|
+
self.stop_daemon(widget_id)
|
|
373
|
+
# 2. 启动需要的daemon(未运行、或因上述原因被重启)
|
|
374
|
+
for widget_id, widget_info in widget_map.items():
|
|
375
|
+
is_block = widget_info.get("isBlock", False)
|
|
376
|
+
version = widget_info.get("version", "1.0")
|
|
377
|
+
if not is_block and self.should_start_daemon(widget_id):
|
|
378
|
+
status = self.get_daemon_status(widget_id)
|
|
379
|
+
daemon_info = config.get(widget_id)
|
|
380
|
+
# 未运行 或 版本号不一致(仅当daemon正在运行时才判断)
|
|
381
|
+
if (not status.get("running", False)) or (daemon_info and daemon_info.get("running", False) and daemon_info.get("version", None) != version):
|
|
382
|
+
self.start_daemon(widget_id, widget_info)
|
|
383
|
+
|
|
384
|
+
# 建议在start_all_daemons后调用一次同步
|
|
385
|
+
def start_all_daemons(self):
|
|
386
|
+
"""启动所有应该启动的常驻进程"""
|
|
387
|
+
widget_map = store.widgetMap()
|
|
388
|
+
for widget_id in widget_map:
|
|
389
|
+
widget_info = widget_map[widget_id]
|
|
390
|
+
if self.should_start_daemon(widget_id) and not widget_info.get("isBlock", False):
|
|
391
|
+
self.start_daemon(widget_id, widget_info)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
# 创建全局实例
|
|
395
|
+
daemon_manager = DaemonManager()
|