120 lines
3.8 KiB
Python
120 lines
3.8 KiB
Python
"""Windows development server controls for the src application."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import subprocess
|
||
import sys
|
||
import time
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parent
|
||
SOURCE_DIR = ROOT / "src"
|
||
PORT = 8000
|
||
|
||
|
||
def listener_pid() -> int | None:
|
||
result = subprocess.run(
|
||
["powershell", "-NoProfile", "-Command", f"(Get-NetTCPConnection -LocalPort {PORT} -State Listen -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty OwningProcess)"],
|
||
capture_output=True,
|
||
text=True,
|
||
check=False,
|
||
)
|
||
try:
|
||
return int(result.stdout.strip())
|
||
except ValueError:
|
||
return None
|
||
|
||
|
||
def command_line(pid: int) -> str:
|
||
result = subprocess.run(
|
||
["powershell", "-NoProfile", "-Command", f"(Get-CimInstance Win32_Process -Filter 'ProcessId = {pid}').CommandLine"],
|
||
capture_output=True,
|
||
text=True,
|
||
check=False,
|
||
)
|
||
return result.stdout.strip()
|
||
|
||
|
||
def is_src_server(pid: int) -> bool:
|
||
command = command_line(pid).lower()
|
||
return "-m http.server" in command and str(SOURCE_DIR).lower() in command
|
||
|
||
|
||
def terminate_process(pid: int) -> bool:
|
||
result = subprocess.run(
|
||
["taskkill", "/PID", str(pid), "/T", "/F"],
|
||
stdout=subprocess.DEVNULL,
|
||
stderr=subprocess.DEVNULL,
|
||
check=False,
|
||
)
|
||
return result.returncode == 0
|
||
|
||
|
||
def start() -> None:
|
||
listener = listener_pid()
|
||
if listener:
|
||
if is_src_server(listener):
|
||
print(f"服务已运行:http://localhost:{PORT}/(PID {listener})")
|
||
return
|
||
|
||
answer = input(
|
||
f"端口 {PORT} 已被其他程序使用(PID {listener})。"
|
||
"是否关闭该进程并强制启动?[y/N]: "
|
||
).strip().lower()
|
||
if answer not in ("y", "yes"):
|
||
print("已取消启动。")
|
||
return
|
||
if not terminate_process(listener):
|
||
print(f"无法关闭 PID {listener},请以管理员身份运行终端后重试。")
|
||
return
|
||
time.sleep(0.3)
|
||
if listener_pid():
|
||
print(f"端口 {PORT} 仍被占用,无法启动服务。")
|
||
return
|
||
print(f"已关闭 PID {listener},正在启动服务。")
|
||
|
||
process = subprocess.Popen(
|
||
[sys.executable, "-m", "http.server", str(PORT), "--directory", str(SOURCE_DIR)],
|
||
cwd=ROOT,
|
||
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS,
|
||
stdout=subprocess.DEVNULL,
|
||
stderr=subprocess.DEVNULL,
|
||
)
|
||
time.sleep(0.3)
|
||
if listener_pid() == process.pid:
|
||
print(f"服务已启动:http://localhost:{PORT}/(PID {process.pid})")
|
||
else:
|
||
print("服务启动失败,请确认 Python 可用且端口未被占用。")
|
||
|
||
|
||
def stop() -> None:
|
||
listener = listener_pid()
|
||
if not listener or not is_src_server(listener):
|
||
print("src 开发服务器未启动。")
|
||
return
|
||
|
||
if terminate_process(listener):
|
||
print("服务已停止。")
|
||
else:
|
||
print(f"无法停止 PID {listener},请以管理员身份运行终端后重试。")
|
||
|
||
|
||
def status() -> None:
|
||
listener = listener_pid()
|
||
if listener and is_src_server(listener):
|
||
print(f"服务运行中:http://localhost:{PORT}/(PID {listener})")
|
||
else:
|
||
print("src 开发服务器未启动。")
|
||
|
||
|
||
def main() -> None:
|
||
parser = argparse.ArgumentParser(description="控制 src 的本地 HTTP 开发服务器。")
|
||
parser.add_argument("command", choices=("start", "stop", "status"), help="执行的操作")
|
||
args = parser.parse_args()
|
||
{"start": start, "stop": stop, "status": status}[args.command]()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|