Skip to content
Merged
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
12 changes: 6 additions & 6 deletions CHANGELOG/v2.3.2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@ v2.3 - Shiroko (砂狼白子) release 3

**为何选择新架构?**

- ✅ **跨平台兼容性**更强
- ✅ **界面流畅度**更高
- ✅ **长期可维护性**更稳
- ✅ **跨平台兼容性**更强
- ✅ **界面流畅度**更高
- ✅ **长期可维护性**更稳

新架构将助力我们更好地实现「简洁、公平、多功能」的核心目标,并在校园点名、抽奖、游戏、决策等场景中保持高效与易用。

**开发计划**

- 持续在 GitHub 更新进度与设计方案
- 测试版本功能稳定后开放下载
- 欢迎第一时间体验与验证
- 持续在 GitHub 更新进度与设计方案
- 测试版本功能稳定后开放下载
- 欢迎第一时间体验与验证

感谢一直以来的支持与信任,让我们共同迎接这次全面焕新!

Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG/v2.3.5/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ v2.3 - Shiroko (砂狼白子) release 4

## 🐛 修复问题

- 修复 **主题页面** 无法显示的问题
- 修复 **主题页面** 无法显示的问题
- 修复 **设置窗口** 重启后无法打开的问题
- 修复 **闪抽** 在随机抽取模式下,无法正常工作的问题

Expand Down
37 changes: 33 additions & 4 deletions app/common/lottery/lottery_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,8 +450,10 @@ def get_random_items(self, count):

group_name = ""
student_name = ""
student_id = 0
if self.current_group_index == 1:
raw_group = system_random.choice(candidates).get("name", "")
selected_candidate = system_random.choice(candidates)
raw_group = selected_candidate.get("name", "")
include_group = show_random in (0, 1, 2, 5, 6, 7, 8, 9)
include_name = show_random in (0, 1, 2, 3, 4, 7, 8, 9, 10, 11)

Expand All @@ -464,13 +466,25 @@ def get_random_items(self, count):
if group_members:
selected_member = system_random.choice(group_members)
student_name = (selected_member or {}).get("name", "")
try:
student_id = int(
(selected_member or {}).get("id", 0) or 0
)
except Exception:
student_id = 0
if not student_name:
student_name = raw_group
else:
student_name = system_random.choice(candidates).get("name", "")
selected_candidate = system_random.choice(candidates)
student_name = selected_candidate.get("name", "")
try:
student_id = int(selected_candidate.get("id", 0) or 0)
except Exception:
student_id = 0

prize_copy["ipc_group_name"] = str(group_name or "")
prize_copy["ipc_student_name"] = str(student_name or "")
prize_copy["student_id"] = student_id
if group_name or student_name:
prize_copy["name"] = self._format_prize_student_text(
prize_name, group_name, student_name, show_random
Expand Down Expand Up @@ -1125,9 +1139,13 @@ def draw_random(widget):
for p in prizes or []:
if not isinstance(p, dict):
continue
try:
sid = int(p.get("student_id", 0) or 0)
except Exception:
sid = 0
ipc_selected_students.append(
{
"student_id": 0,
"student_id": sid,
"student_name": str(p.get("ipc_student_name", "") or ""),
"display_text": str(
p.get("ipc_display_text", p.get("name", "")) or ""
Expand All @@ -1139,7 +1157,18 @@ def draw_random(widget):
),
}
)
selected_prizes = [(p["id"], p["name"], p.get("exist", True)) for p in prizes]
selected_prizes = []
for p in prizes:
try:
display_num = int(p.get("student_id", 0) or 0)
except Exception:
display_num = 0
if not display_num:
try:
display_num = int(p.get("id", 0) or 0)
except Exception:
display_num = 0
selected_prizes.append((display_num, p["name"], p.get("exist", True)))

display_result_animated(
widget,
Expand Down
15 changes: 11 additions & 4 deletions app/common/music/music_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
# ==================================================

import threading
import time
from typing import Optional
import numpy as np
from loguru import logger
Expand Down Expand Up @@ -287,7 +286,10 @@ def _play_music_worker(self, music_path: str, loop: bool) -> None:
if self._fade_out_remaining_time >= chunk_duration:
# 整个块都在渐出范围内
fade_out_ratio = 1.0 - (
(self._fade_out_duration - self._fade_out_remaining_time)
(
self._fade_out_duration
- self._fade_out_remaining_time
)
/ self._fade_out_duration
)
chunk *= self._volume * fade_out_ratio
Expand All @@ -299,10 +301,15 @@ def _play_music_worker(self, music_path: str, loop: bool) -> None:
)
if fade_out_samples > 0:
fade_out_ratio = 1.0 - (
(self._fade_out_duration - self._fade_out_remaining_time)
(
self._fade_out_duration
- self._fade_out_remaining_time
)
/ self._fade_out_duration
)
chunk[:fade_out_samples] *= self._volume * fade_out_ratio
chunk[:fade_out_samples] *= (
self._volume * fade_out_ratio
)
chunk[fade_out_samples:] = 0
else:
chunk[:] = 0
Expand Down
62 changes: 58 additions & 4 deletions app/core/single_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,61 @@ def check_single_instance() -> Tuple[Optional[QSharedMemory], bool]:
_activate_existing_instance()
return shared_memory, False
else:
logger.exception("无法附加到共享内存")
return shared_memory, False
logger.warning("无法附加到共享内存,尝试检测服务器状态")
if _check_server_alive():
logger.info("检测到活跃的服务器,已有实例正在运行")
_activate_existing_instance()
return shared_memory, False
else:
logger.warning("服务器不可连接,清理残留资源后重新启动")
_cleanup_stale_resources()
if shared_memory.create(1):
logger.info("单实例检查通过(清理后重新创建)")
return shared_memory, True
else:
logger.exception("清理后仍无法创建共享内存")
return shared_memory, False

logger.info("单实例检查通过,可以安全启动程序")
return shared_memory, True


def _check_server_alive() -> bool:
"""检查本地服务器是否在监听(可连接)

Returns:
bool: 服务器是否可连接
"""
local_socket = QLocalSocket()
local_socket.connectToServer(SHARED_MEMORY_KEY)
connected = local_socket.waitForConnected(500)
if connected:
local_socket.disconnectFromServer()
return True
return False


def _cleanup_stale_resources() -> None:
"""清理残留的单实例资源"""
QLocalServer.removeServer(SHARED_MEMORY_KEY)
logger.debug("已清理残留的socket资源")

_cleanup_stale_shared_memory()


def _cleanup_stale_shared_memory() -> None:
"""清理残留的共享内存段(POSIX系统需要)

在POSIX系统上,QSharedMemory在进程崩溃后不会自动清理。
需要先attach再detach来触发清理。
"""
stale_memory = QSharedMemory(SHARED_MEMORY_KEY)
if stale_memory.attach():
logger.debug("检测到残留的共享内存段,正在清理")
stale_memory.detach()
logger.debug("已清理残留的共享内存段")


def _activate_existing_instance() -> bool:
"""激活已有实例

Expand Down Expand Up @@ -74,8 +122,14 @@ def setup_local_server(
"""
server = QLocalServer()
if not server.listen(SHARED_MEMORY_KEY):
logger.exception(f"无法启动本地服务器: {server.errorString()}")
return None
error_string = server.errorString()
logger.warning(f"本地服务器启动失败,尝试清理残留socket: {error_string}")

QLocalServer.removeServer(SHARED_MEMORY_KEY)

if not server.listen(SHARED_MEMORY_KEY):
logger.exception(f"无法启动本地服务器: {server.errorString()}")
return None

server.newConnection.connect(
lambda: _handle_new_connection(server, main_window, float_window, url_handler)
Expand Down
4 changes: 2 additions & 2 deletions app/view/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ class SomeSettingPage(QWidget):
super().__init__(parent)
self._init_ui()
self._load_settings()

def _init_ui(self):
# Setup layout and widgets
pass

def _load_settings(self):
# Load from settings_access
pass
Expand Down
55 changes: 0 additions & 55 deletions requirements-linux.txt

This file was deleted.

59 changes: 0 additions & 59 deletions requirements-windows.txt

This file was deleted.

Loading