Files

160 lines
5.6 KiB
Python
Raw Permalink Normal View History

2026-06-22 11:01:15 +08:00
"""
将主机端 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()