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.
Files changed (31) hide show
  1. {ryry_cli-3.4 → ryry_cli-4.0}/PKG-INFO +3 -2
  2. {ryry_cli-3.4 → ryry_cli-4.0}/ryry/constant.py +2 -2
  3. ryry_cli-4.0/ryry/daemon_base.py +131 -0
  4. ryry_cli-4.0/ryry/daemon_manager.py +395 -0
  5. {ryry_cli-3.4 → ryry_cli-4.0}/ryry/main.py +72 -6
  6. {ryry_cli-3.4 → ryry_cli-4.0}/ryry/ryry_server_socket.py +283 -279
  7. {ryry_cli-3.4 → ryry_cli-4.0}/ryry/ryry_service.py +47 -9
  8. {ryry_cli-3.4 → ryry_cli-4.0}/ryry/ryry_webapi.py +18 -20
  9. {ryry_cli-3.4 → ryry_cli-4.0}/ryry/ryry_widget.py +64 -3
  10. ryry_cli-4.0/ryry/script_template/daemon.py +59 -0
  11. {ryry_cli-3.4 → ryry_cli-4.0}/ryry/script_template/main.py +1 -1
  12. {ryry_cli-3.4 → ryry_cli-4.0}/ryry/server_func.py +4 -3
  13. ryry_cli-4.0/ryry/shared_memory.py +756 -0
  14. {ryry_cli-3.4 → ryry_cli-4.0}/ryry/store.py +13 -15
  15. {ryry_cli-3.4 → ryry_cli-4.0}/ryry/task.py +89 -56
  16. {ryry_cli-3.4 → ryry_cli-4.0}/ryry/taskUtils.py +7 -7
  17. {ryry_cli-3.4 → ryry_cli-4.0}/ryry/upload.py +183 -32
  18. {ryry_cli-3.4 → ryry_cli-4.0}/ryry/utils.py +19 -5
  19. {ryry_cli-3.4 → ryry_cli-4.0}/ryry_cli.egg-info/PKG-INFO +3 -2
  20. {ryry_cli-3.4 → ryry_cli-4.0}/ryry_cli.egg-info/SOURCES.txt +4 -0
  21. {ryry_cli-3.4 → ryry_cli-4.0}/ryry_cli.egg-info/requires.txt +1 -0
  22. {ryry_cli-3.4 → ryry_cli-4.0}/setup.py +3 -2
  23. {ryry_cli-3.4 → ryry_cli-4.0}/LICENSE +0 -0
  24. {ryry_cli-3.4 → ryry_cli-4.0}/README.md +0 -0
  25. {ryry_cli-3.4 → ryry_cli-4.0}/ryry/__init__.py +0 -0
  26. {ryry_cli-3.4 → ryry_cli-4.0}/ryry/script_template/__init__.py +0 -0
  27. {ryry_cli-3.4 → ryry_cli-4.0}/ryry/script_template/run.py +0 -0
  28. {ryry_cli-3.4 → ryry_cli-4.0}/ryry_cli.egg-info/dependency_links.txt +0 -0
  29. {ryry_cli-3.4 → ryry_cli-4.0}/ryry_cli.egg-info/entry_points.txt +0 -0
  30. {ryry_cli-3.4 → ryry_cli-4.0}/ryry_cli.egg-info/top_level.txt +0 -0
  31. {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.4
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.4
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
  ===============================================
@@ -1,6 +1,6 @@
1
1
  #!!!!! do not change this file !!!!!
2
- app_version="3.4"
3
- app_bulld_anchor="Noh_2025-06-06 18:21:45.464225"
2
+ app_version="4.0"
3
+ app_bulld_anchor="Noh_2025-07-15 16:31:49.444907"
4
4
  app_name="ryry-cli"
5
5
  import sys, os
6
6
  if getattr(sys, 'frozen', False):
@@ -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()