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
19 changes: 19 additions & 0 deletions module/webui/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
from module.logger import logger
from module.config.utils import DEFAULT_CONFIG_NAME


def is_demo_mode():
return os.environ.get("DEMO") == "1"

Comment on lines 27 to +32

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

module/webui/api.pymodule/webui/app.py 中都重复定义了 is_demo_mode() 函数。此外,在 process_manager.pyapp.py(如第 5095 行)以及 config.py 中仍然直接使用了 os.environ.get("DEMO") == "1"。\n\n为了提高代码的可维护性和一致性,建议将 is_demo_mode() 统一移动到公共工具模块(例如 module/config/utils.py,该模块已被这些文件广泛导入),并在所有相关地方统一调用。

Suggested change
from module.config.utils import DEFAULT_CONFIG_NAME
def is_demo_mode():
return os.environ.get("DEMO") == "1"
from module.config.utils import DEFAULT_CONFIG_NAME, is_demo_mode


def api_cl1_stats(request):
try:
from module.statistics.opsi_month import get_opsi_stats
Expand Down Expand Up @@ -968,6 +973,13 @@ def _key_to_android_keycode(key):

async def ws_live_screenshot(websocket):
await websocket.accept()
if is_demo_mode():
await websocket.send_text(json.dumps({
"type": "error",
"message": "DEMO=1,实时预览已禁用,避免初始化设备资源。",
}))
await websocket.close()
return

instance = websocket.query_params.get("instance", DEFAULT_CONFIG_NAME)
mode = websocket.query_params.get("mode", "auto").lower()
Expand Down Expand Up @@ -1265,6 +1277,13 @@ def stderr_reader():

async def ws_live_control(websocket):
await websocket.accept()
if is_demo_mode():
await websocket.send_text(json.dumps({
"type": "error",
"message": "DEMO=1,实时控制已禁用,避免初始化设备资源。",
}))
await websocket.close()
return
instance = websocket.query_params.get("instance", DEFAULT_CONFIG_NAME)
fallback = None

Expand Down
17 changes: 10 additions & 7 deletions module/webui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
"当前配置允许所有设备访问,但自动生成密码失败,请手动在 config/deploy.yaml 设置 Password 后重启。"
)
WEBUI_AUTO_PASSWORD_FILE = "password.txt"
DEMO_DEVICE_ID_TEXT = "此程序是为了演示用途构建的版本/This application is a version built for demonstration purposes."


def is_demo_mode():
Expand Down Expand Up @@ -3184,7 +3185,7 @@ def set_group(self, group, arg_dict, config, task):

# 在掉落记录组中显示可复制的设备ID
if group_name == "DropRecord":
device_id = get_device_id()
device_id = DEMO_DEVICE_ID_TEXT if is_demo_mode() else get_device_id()
put_html(build_copyable_device_id(device_id))

return len(output_list)
Expand All @@ -3205,13 +3206,12 @@ def set_navigator(self, group):

def _alas_start(self):
self.alas.start(None, updater.event)
if os.environ.get("DEMO") == "1":
threading.Timer(5, self.alas.stop).start()

def _simulator_start(self):
if is_demo_mode():
logger.info("DEMO=1,跳过大世界模拟器启动。")
return
self.simulator.start()
if os.environ.get("DEMO") == "1":
threading.Timer(5, self.simulator.interrupt).start()

@use_scope("content", clear=True)
def alas_overview(self) -> None:
Expand Down Expand Up @@ -3392,8 +3392,9 @@ def alas_overview(self) -> None:
# version
local_commit = updater.get_commit(short_sha1=True)
version = local_commit[0] if local_commit and local_commit[0] else "Unknown"
device_id = DEMO_DEVICE_ID_TEXT if is_demo_mode() else get_device_id()
put_scope("log-container", [put_scope("log", [put_html("")])]).style(
f"--device-id: '{get_device_id()}'; --version: 'Ver.{version}';"
f"--device-id: '{device_id}'; --version: 'Ver.{version}';"
)

log.console.width = log.get_width()
Expand Down Expand Up @@ -5088,7 +5089,7 @@ def startup():
task_handler.start()
if State.deploy_config.DiscordRichPresence:
init_discord_rpc()
if State.deploy_config.StartOcrServer:
if State.deploy_config.StartOcrServer and not is_demo_mode():
start_ocr_server_process(State.deploy_config.OcrServerPort)
if State.deploy_config.EnableRemoteAccess and (
State.deploy_config.Password is not None or os.environ.get("DEMO") == "1"
Expand Down Expand Up @@ -5169,6 +5170,8 @@ def app():
static_path = os.getcwd()

def _block_restricted_device():
if is_demo_mode():
return False
if get_device_id() not in RESTRICTED_DEVICE_IDS:
return False
popup(
Expand Down
12 changes: 12 additions & 0 deletions module/webui/process_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ def state(self) -> int:
if update_tail_hit:
return 4
return 2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): 与特定日志消息字符串的紧密耦合可能会比较脆弱。

这个条件依赖日志中的字面子串 "此版本为演示用途",将行为绑定到一个面向用户的本地化文案上,而这些文案可能会发生变化(文案修改、国际化、重写等)。相较于匹配具体的消息文本,更推荐使用结构化且稳定的信号(例如专门的非本地化标签/前缀或日志中的机器可读标记)。

建议实现方式:

            elif DEMO_LOG_MARKER in s:
                return 2
        set_func_logger(func=q.put)

        DEMO_LOG_MARKER = "[DEMO_MODE]"

        if os.environ.get("DEMO") == "1":

`) 要求所有产生与演示相关日志的代码都包含这个标记。

以下是具体修改:

<file_operations>
<file_operation operation="edit" file_path="module/webui/process_manager.py">
<<<<<<< SEARCH
elif "此版本为演示用途" in s:
return 2

        elif DEMO_LOG_MARKER in s:
            return 2

REPLACE

<<<<<<< SEARCH
set_func_logger(func=q.put)

    if os.environ.get("DEMO") == "1":

=======
set_func_logger(func=q.put)

    DEMO_LOG_MARKER = "[DEMO_MODE]"

    if os.environ.get("DEMO") == "1":

REPLACE
</file_operation>
</file_operations>

<additional_changes>

  1. 在产生该日志行的地方(原来包含 "此版本为演示用途" 的那一行),以一种稳定的方式在日志信息中前置或插入 DEMO_LOG_MARKER 字符串 "[DEMO_MODE]"(例如:" [DEMO_MODE] 此版本为演示用途...")。
  2. 如果存在多条与演示相关的日志消息,建议统一使用同一个标记,以简化消费端逻辑。
  3. 如果你更偏好通过配置而不是硬编码标记,后续可以将 DEMO_LOG_MARKER 重构为模块级常量或配置项,但仍需保持其非本地化且稳定。
Original comment in English

suggestion (bug_risk): Tight coupling to a specific log message string may be brittle.

This condition depends on the literal substring "此版本为演示用途" in the log, tying behavior to a localized, human-facing message that may change (copy edits, i18n, rewording). Prefer a structured, stable signal (e.g., a dedicated non-localized tag/prefix or machine-readable marker in the log) instead of matching the exact message text.

Suggested implementation:

            elif DEMO_LOG_MARKER in s:
                return 2
        set_func_logger(func=q.put)

        DEMO_LOG_MARKER = "[DEMO_MODE]"

        if os.environ.get("DEMO") == "1":

`) that any demo-related log producers include this marker.

Here are the concrete edits:

<file_operations>
<file_operation operation="edit" file_path="module/webui/process_manager.py">
<<<<<<< SEARCH
elif "此版本为演示用途" in s:
return 2

        elif DEMO_LOG_MARKER in s:
            return 2

REPLACE

<<<<<<< SEARCH
set_func_logger(func=q.put)

    if os.environ.get("DEMO") == "1":

=======
set_func_logger(func=q.put)

    DEMO_LOG_MARKER = "[DEMO_MODE]"

    if os.environ.get("DEMO") == "1":

REPLACE
</file_operation>
</file_operations>

<additional_changes>

  1. Wherever this log line is produced (the one that previously contained "此版本为演示用途"), prepend or inject the DEMO_LOG_MARKER string "[DEMO_MODE]" into the log message in a stable way (e.g. " [DEMO_MODE] 此版本为演示用途...").
  2. If there are multiple demo-related messages, standardize on using this same marker for all of them to keep the consumer logic simple.
  3. If you prefer configuration over a hardcoded marker, you can later refactor DEMO_LOG_MARKER into a module-level constant or configuration setting, but keep it non-localized and stable.

elif "此版本为演示用途" in s:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

这里存在一个潜在的竞争条件(Race Condition)。在 ProcessManager 中,_thread_log_queue_handler 线程是通过 while self.alive 循环来读取日志队列的。\n\n当子进程在 run_process 中执行完 logger.info("此版本为演示用途") 并执行 return 退出后,self.alive 会立即变为 False。这可能导致 _thread_log_queue_handler 线程在还没来得及从队列中读取最后这条日志时就终止了循环,从而导致 "此版本为演示用途" 无法被追加到 self.renderables 中。\n\n如果发生这种情况,state 将无法匹配到该关键字,进而错误地返回状态 3(异常/警告)而不是 2(未运行)。\n\n建议:\n在 _thread_log_queue_handler 退出 while self.alive 循环后,增加一个清空队列中剩余日志的逻辑。例如:\npython\n# 在 while self.alive 循环结束后\nwhile not self._renderable_queue.empty():\n try:\n log = self._renderable_queue.get_nowait()\n self.renderables.append(log)\n except queue.Empty:\n break\n\n由于 _thread_log_queue_handler 的定义不在当前 diff 中,请在对应位置手动进行修复。

return 2
elif update_tail_hit:
return 4
else:
Expand Down Expand Up @@ -230,6 +232,16 @@ def run_process(
logger.removeHandler(console_hdlr)
set_func_logger(func=q.put)

if os.environ.get("DEMO") == "1":
logger.info("Log3")
time.sleep(1)
logger.info("Log2")
time.sleep(1)
logger.info("Log1")
time.sleep(1)
logger.info("此版本为演示用途")
Comment on lines +235 to +242

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: 演示模式检测是临时实现的,而不是通过共享的辅助函数来完成。

其他模块使用 is_demo_mode() 辅助函数,而这个文件则直接读取 os.environ.get("DEMO") == "1"。为了在将来修改演示模式逻辑时(例如添加额外条件或配置)避免行为出现偏差,请在这里复用共享辅助函数,或者引入一个小的本地辅助函数并委托给它。

建议实现方式:

        set_func_logger(func=q.put)


        if is_demo_mode():
            logger.info("Log3")
            time.sleep(1)
            logger.info("Log2")
            time.sleep(1)
            logger.info("Log1")
            time.sleep(1)
            logger.info("此版本为演示用途")
            return
  1. 确保在 module/webui/process_manager.py 顶部从其他模块使用的相同位置导入 is_demo_mode(例如 from module.webui.utils import is_demo_mode 或等价路径)。
  2. 如果该文件中还有其他地方直接读取 os.environ.get("DEMO"),也请更新为使用 is_demo_mode() 以保持行为一致。
Original comment in English

suggestion: Demo-mode detection is implemented ad hoc instead of using a shared helper.

Other modules use an is_demo_mode() helper, while this file reads os.environ.get("DEMO") == "1" directly. To avoid future behavior drift if demo-mode logic changes (e.g., extra conditions or config), please reuse the shared helper here or introduce a small local helper that delegates to it.

Suggested implementation:

        set_func_logger(func=q.put)


        if is_demo_mode():
            logger.info("Log3")
            time.sleep(1)
            logger.info("Log2")
            time.sleep(1)
            logger.info("Log1")
            time.sleep(1)
            logger.info("此版本为演示用途")
            return
  1. Ensure is_demo_mode is imported at the top of module/webui/process_manager.py from the same place other modules use (e.g., from module.webui.utils import is_demo_mode or equivalent).
  2. If there are other direct reads of os.environ.get("DEMO") in this file, update them to use is_demo_mode() as well to keep behavior consistent.

return

from module.config.config import AzurLaneConfig

# 移除伪造的 PIL 模块,子进程需要使用真正的 PIL
Expand Down
Loading