Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 85 additions & 29 deletions app/common/IPC_URL/csharp_ipc_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
self.client_thread: Optional[threading.Thread] = None
self.is_running = False
self.is_connected = False
self._stop_event = threading.Event()
self._disconnect_logged = False # 跟踪是否已记录断连日志
self._last_on_class_left_log_time = 0 # 上次记录距离上课时间的时间

Expand All @@ -80,6 +81,7 @@
return True

try:
self._stop_event.clear()
self.client_thread = threading.Thread(
target=self._run_client, daemon=False
)
Expand All @@ -92,9 +94,24 @@

def stop_ipc_client(self):
"""停止 C# IPC 客户端"""
if not self.is_running:
return

logger.debug("正在停止 C# IPC 客户端...")
self.is_running = False
self._stop_event.set()

if self.client_thread and self.client_thread.is_alive():
self.client_thread.join(timeout=1)
logger.debug("等待 C# IPC 线程结束...")
# 给予足够的时间正常退出,因为 daemon=False
self.client_thread.join(timeout=1.0)
if self.client_thread.is_alive():
logger.warning("C# IPC 线程未能在超时时间内完全结束,可能由于 .NET 调用阻塞")

Check failure on line 110 in app/common/IPC_URL/csharp_ipc_handler.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (W293)

app/common/IPC_URL/csharp_ipc_handler.py:110:1: W293 Blank line contains whitespace
# 线程结束后再清理资源,避免竞态条件
self.ipc_client = None
self.is_connected = False
logger.debug("C# IPC 客户端停止指令已发出")

def send_notification(
self,
Expand Down Expand Up @@ -203,44 +220,82 @@
f"上课 {lessonSc.CurrentSubject.Name} 时间: {lessonSc.CurrentTimeLayoutItem}"
)

def _run_client(self):
"""运行 C# IPC 客户端"""

async def client():
"""异步客户端"""

self.ipc_client = IpcClient()
self.ipc_client.JsonIpcProvider.AddNotifyHandler(
IpcRoutedNotifyIds.OnClassNotifyId,
Action(lambda: self._on_class_test()),
)

task = self.ipc_client.Connect()
await loop.run_in_executor(None, lambda: task.Wait())
self.is_connected = True

while self.is_running:
await asyncio.sleep(1)

if not self._check_alive():
if not self._disconnect_logged:
logger.debug("C# IPC 断连!重连...")
self._disconnect_logged = True
try:
self.ipc_client = IpcClient()
self.ipc_client.JsonIpcProvider.AddNotifyHandler(
IpcRoutedNotifyIds.OnClassNotifyId,
Action(lambda: self._on_class_test()),
)

task = self.ipc_client.Connect()
# 优化:在等待连接时定期检查 is_running 标志,以便快速响应退出请求
# 避免在 .NET 内部 Wait() 导致线程无法被 Python 正常终止
while self.is_running and not task.IsCompleted:
await asyncio.sleep(0.1)

Check failure on line 240 in app/common/IPC_URL/csharp_ipc_handler.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (W293)

app/common/IPC_URL/csharp_ipc_handler.py:240:1: W293 Blank line contains whitespace
if not self.is_running:
return

Check failure on line 243 in app/common/IPC_URL/csharp_ipc_handler.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (W293)

app/common/IPC_URL/csharp_ipc_handler.py:243:1: W293 Blank line contains whitespace
if task.IsFaulted:
logger.error(f"C# IPC 连接失败: {task.Exception}")
self.is_connected = False

task = self.ipc_client.Connect()
await loop.run_in_executor(None, task.Wait)
self.is_connected = True
self._disconnect_logged = False

self.ipc_client = None
self.is_connected = False
return

Check failure on line 248 in app/common/IPC_URL/csharp_ipc_handler.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (W293)

app/common/IPC_URL/csharp_ipc_handler.py:248:1: W293 Blank line contains whitespace
self.is_connected = True

while self.is_running:
# 缩短轮询间隔,提高退出响应速度 (每次睡眠 0.1s,共 0.5s)
for _ in range(5):
if not self.is_running:
break
await asyncio.sleep(0.1)

if not self.is_running:
break

if not self._check_alive():
if not self._disconnect_logged:
logger.debug("C# IPC 断连!重连...")
self._disconnect_logged = True
self.is_connected = False

task = self.ipc_client.Connect()
while self.is_running and not task.IsCompleted:
await asyncio.sleep(0.1)

Check failure on line 270 in app/common/IPC_URL/csharp_ipc_handler.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (W293)

app/common/IPC_URL/csharp_ipc_handler.py:270:1: W293 Blank line contains whitespace
if not self.is_running:
break

Check failure on line 273 in app/common/IPC_URL/csharp_ipc_handler.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (W293)

app/common/IPC_URL/csharp_ipc_handler.py:273:1: W293 Blank line contains whitespace
if task.IsFaulted:
continue

Check failure on line 276 in app/common/IPC_URL/csharp_ipc_handler.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (W293)

app/common/IPC_URL/csharp_ipc_handler.py:276:1: W293 Blank line contains whitespace
self.is_connected = True
self._disconnect_logged = False
except Exception as e:
if self.is_running:
logger.error(f"C# IPC 客户端运行出错: {e}")
finally:
# 在线程内部安全地释放资源
try:
if self.ipc_client and hasattr(self.ipc_client, "Dispose"):
self.ipc_client.Dispose()
logger.debug("C# IPC 客户端资源已释放 (Dispose)")

Check notice on line 287 in app/common/IPC_URL/csharp_ipc_handler.py

View check run for this annotation

codefactor.io / CodeFactor

app/common/IPC_URL/csharp_ipc_handler.py#L226-L287

Complex Method
except Exception as e:
logger.debug(f"释放 C# IPC 客户端资源时出错: {e}")
self.is_connected = False

# 启动新的 asyncio 事件循环
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(client())
loop.close()
try:

Check notice on line 295 in app/common/IPC_URL/csharp_ipc_handler.py

View check run for this annotation

codefactor.io / CodeFactor

app/common/IPC_URL/csharp_ipc_handler.py#L223-L295

Complex Method
loop.run_until_complete(client())
finally:
loop.close()

def _check_alive(self) -> bool:
"""客户端是否正常连接"""
Expand Down Expand Up @@ -291,6 +346,7 @@

def stop_ipc_client(self):
"""停止 C# IPC 客户端"""
logger.debug("C# IPC 处理器未启用,无需停止")
pass

def send_notification(
Expand Down
22 changes: 14 additions & 8 deletions app/common/safety/secure_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,13 @@ def read_secrets() -> dict:
# 不存在则创建空文件
if not os.path.exists(p):
ensure_dir(os.path.dirname(p))
with open(p, "wb") as f:
f.write(b"")
_set_hidden(str(p))
logger.debug(f"创建空安全配置文件:{p}")
try:
with open(p, "w", encoding="utf-8") as f:
json.dump({}, f)
_set_hidden(str(p))
logger.debug(f"创建空安全配置文件:{p}")
except Exception as e:
logger.warning(f"创建安全配置文件失败: {e}")
return {}
if os.path.exists(p):
try:
Expand Down Expand Up @@ -185,10 +188,13 @@ def read_behind_scenes_settings() -> dict:
p = get_settings_path("behind_scenes.json")
if not os.path.exists(p):
ensure_dir(os.path.dirname(p))
with open(p, "wb") as f:
f.write(b"")
_set_hidden(str(p))
logger.debug(f"创建空内幕设置文件:{p}")
try:
with open(p, "w", encoding="utf-8") as f:
json.dump({}, f)
_set_hidden(str(p))
logger.debug(f"创建空内幕设置文件:{p}")
except Exception as e:
logger.warning(f"创建内幕设置文件失败: {e}")
return {}
if os.path.exists(p):
try:
Expand Down
21 changes: 14 additions & 7 deletions app/common/shortcut/shortcut_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,17 @@ def is_enabled(self) -> bool:

def cleanup(self):
"""清理所有快捷键"""
logger.info("清理所有快捷键")
for config_key, hotkey in self.shortcuts.items():
try:
keyboard.remove_hotkey(hotkey)
except Exception as e:
logger.error(f"注销快捷键失败 {config_key}: {e}")
self.shortcuts.clear()
if self.shortcuts:
logger.info(f"清理已注册的 {len(self.shortcuts)} 个快捷键")
for config_key, hotkey in self.shortcuts.items():
try:
keyboard.remove_hotkey(hotkey)
except Exception as e:
logger.error(f"注销快捷键失败 {config_key}: {e}")
self.shortcuts.clear()

# 无论是否有已注册快捷键,都尝试清理全局钩子,确保 keyboard 库的监听线程能正确关闭
try:
keyboard.unhook_all()
except Exception as e:
logger.warning(f"清理全局键盘钩子失败: {e}")
13 changes: 12 additions & 1 deletion app/core/window_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@
self.settings_window: Optional["QWidget"] = None
self.float_window: Optional["QWidget"] = None
self.url_handler: Optional = None
self.shared_memory = None

def set_shared_memory(self, shared_memory) -> None:
"""设置共享内存实例

Check failure on line 26 in app/core/window_manager.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (W293)

app/core/window_manager.py:26:1: W293 Blank line contains whitespace
Args:
shared_memory: QSharedMemory 实例
"""
self.shared_memory = shared_memory

def set_url_handler(self, url_handler) -> None:
"""设置URL处理器
Expand All @@ -42,7 +51,9 @@

self.create_float_window()
self.main_window = MainWindow(
float_window=self.float_window, url_handler_instance=self.url_handler
float_window=self.float_window,
url_handler_instance=self.url_handler,
shared_memory=self.shared_memory,
)

self._connect_main_window_signals()
Expand Down
14 changes: 14 additions & 0 deletions app/tools/update_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1818,3 +1818,17 @@ def check_for_updates_on_startup(settings_window=None):
update_check_thread = UpdateCheckThread(settings_window)
update_check_thread.start()
return update_check_thread


def stop_update_check():
"""停止更新检查线程"""
global update_check_thread
if update_check_thread and update_check_thread.isRunning():
logger.debug("停止更新检查线程...")
update_check_thread.terminate() # 直接强制终止
# 给予一定时间正常退出
if not update_check_thread.wait(1000):
logger.warning("更新检查线程未能在超时时间内正常退出,强制终止")
update_check_thread.terminate()
update_check_thread.wait(500)
update_check_thread = None
Loading
Loading