Files
xian_algorithm_new/app/script/copy_data_to_container.py
2026-06-22 11:01:15 +08:00

160 lines
5.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
将主机端 GPKG 和模板文件预拷贝到 Docker 容器本地文件系统。
WSL2 9P 文件系统随机读取极慢(GPKG 62MB 耗时 6-10s,模板 ZIP 也慢),
拷贝到容器内 /data/ 后读取仅需 ~0.5s。
用法:
python app/script/copy_data_to_container.py [--container qgis-server] [--dry-run] [--only gpkg|template]
前置条件:
Docker 容器已启动(docker start qgis-server
"""
import argparse
import os
import subprocess
import sys
import time
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
def _load_config():
"""从 settings.toml 读取配置"""
try:
from config import settings
return {
"gpkg_subdir": getattr(settings, "QGIS_GPKG_DIR", "app/data/gpkg"),
"template_subdir": "app/data/template",
"container": getattr(settings, "QGIS_DOCKER_CONTAINER", "qgis-server"),
"container_gpkg": getattr(settings, "QGIS_DOCKER_GPKG_DIR", "/data/gpkg"),
"container_template": getattr(settings, "QGIS_DOCKER_TEMPLATE_DIR", "/data/template"),
}
except ImportError:
pass
# fallback: 解析 TOML
cfg = {
"gpkg_subdir": "app/data/gpkg",
"template_subdir": "app/data/template",
"container": "qgis-server",
"container_gpkg": "/data/gpkg",
"container_template": "/data/template",
}
toml_path = PROJECT_ROOT / "settings.toml"
if toml_path.exists():
for line in toml_path.read_text(encoding="utf-8").splitlines():
line = line.strip()
for key, prefix in [
("gpkg_subdir", "QGIS_GPKG_DIR"),
("container", "QGIS_DOCKER_CONTAINER"),
("container_gpkg", "QGIS_DOCKER_GPKG_DIR"),
("container_template", "QGIS_DOCKER_TEMPLATE_DIR"),
]:
if line.startswith(prefix) and "=" in line:
cfg[key] = line.split("=", 1)[1].strip().strip('"').strip("'")
return cfg
def _run(cmd, timeout=10, **kwargs):
"""subprocess.run 封装,统一 UTF-8 编码,避免 Windows GBK 报错"""
result = subprocess.run(
cmd, capture_output=True, timeout=timeout,
encoding="utf-8", errors="replace", **kwargs,
)
return result
def _check_container(container: str):
"""检查容器是否运行"""
result = _run(["docker", "inspect", "--format={{.State.Running}}", container], timeout=5)
if (result.stdout or "").strip() != "true":
raise RuntimeError(f"容器 {container} 未运行,请先 docker start {container}")
def _copy_dir_to_container(host_dir: Path, container: str, container_dest: str, label: str, dry_run: bool):
"""拷贝单个目录到容器"""
if not host_dir.is_dir():
print(f" [{label}] 目录不存在,跳过: {host_dir}")
return 0
files = list(host_dir.rglob("*"))
files = [f for f in files if f.is_file()]
if not files:
print(f" [{label}] 目录为空,跳过: {host_dir}")
return 0
total_size = sum(f.stat().st_size for f in files)
print(f" [{label}] 主机目录: {host_dir}")
print(f" 文件数: {len(files)}, 总大小: {total_size / 1024 / 1024:.1f} MB")
print(f" 容器目标: {container}:{container_dest}")
if dry_run:
print(f" [dry-run] 跳过")
return 0
# 确保目标目录存在
parent_dir = container_dest.rsplit("/", 1)[0]
_run(["docker", "exec", container, "mkdir", "-p", parent_dir])
# 清理旧目录
_run(["docker", "exec", container, "rm", "-rf", container_dest])
# docker cp
t0 = time.time()
result = _run(["docker", "cp", str(host_dir), f"{container}:{container_dest}"], timeout=120)
elapsed = time.time() - t0
if result.returncode != 0:
raise RuntimeError(f"[{label}] docker cp 失败: {result.stderr}")
# 验证
verify = _run(["docker", "exec", container, "find", container_dest, "-type", "f"])
stdout = (verify.stdout or "").strip()
count = len([l for l in stdout.splitlines() if l.strip()])
print(f" 拷贝完成: {elapsed:.1f}s, 容器内 {count} 个文件")
return elapsed
def copy_to_container(container: str = None, dry_run: bool = False, only: str = None):
"""将 GPKG 和模板拷贝到容器本地文件系统"""
cfg = _load_config()
if container is None:
container = cfg["container"]
_check_container(container)
t_total = time.time()
print(f"=== 预拷贝静态数据到容器 {container} ===\n")
if only != "template":
host_gpkg = PROJECT_ROOT / cfg["gpkg_subdir"]
_copy_dir_to_container(host_gpkg, container, cfg["container_gpkg"], "GPKG", dry_run)
print()
if only != "gpkg":
host_template = PROJECT_ROOT / cfg["template_subdir"]
_copy_dir_to_container(host_template, container, cfg["container_template"], "模板", dry_run)
elapsed = time.time() - t_total
print(f"\n=== 总耗时: {elapsed:.1f}s ===")
def main():
parser = argparse.ArgumentParser(description="将 GPKG 和模板预拷贝到 Docker 容器本地 FS")
parser.add_argument("--container", default=None, help="Docker 容器名称")
parser.add_argument("--dry-run", action="store_true", help="仅显示信息,不实际拷贝")
parser.add_argument("--only", choices=["gpkg", "template"], help="只拷贝指定类型")
args = parser.parse_args()
try:
copy_to_container(container=args.container, dry_run=args.dry_run, only=args.only)
except Exception as e:
print(f"错误: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()