#!/usr/bin/env python3 from __future__ import annotations import argparse import hashlib import json from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path from urllib.parse import urlparse def parse_args(): p=argparse.ArgumentParser(description="CM95 MySQL vs PostgreSQL PBF comparison server") p.add_argument("--host",default="0.0.0.0") p.add_argument("--port",type=int,default=18889) p.add_argument("--mysql-pbf-root",required=True) p.add_argument("--postgres-pbf-root",required=True) p.add_argument("--mysql-release",default="mysql-current") p.add_argument("--postgres-release",default="postgres-current") return p.parse_args() def sha256(path: Path) -> str: h=hashlib.sha256() with path.open("rb") as f: for b in iter(lambda:f.read(1024*1024),b""): h.update(b) return h.hexdigest() def pbf_inventory(root: Path): return {str(p.relative_to(root)):p for p in root.rglob("*.pbf") if p.is_file()} def main(): args=parse_args() web=Path(__file__).resolve().parent mysql_root=Path(args.mysql_pbf_root).resolve() pg_root=Path(args.postgres_pbf_root).resolve() html=web/"mysql_postgresql_compare_north_kyushu.html" style=web/"resources/style.s52.json" assets=web/"assets" resources=web/"resources" for p in [mysql_root,pg_root,html,style,assets/"maplibre-gl.js",assets/"maplibre-gl.css",resources/"sprite.json",resources/"sprite.png"]: if not p.exists(): raise SystemExit(f"missing: {p}") mi=pbf_inventory(mysql_root) pi=pbf_inventory(pg_root) common=sorted(set(mi)&set(pi)) identical=0 different=[] for rel in common: if mi[rel].stat().st_size==pi[rel].stat().st_size and sha256(mi[rel])==sha256(pi[rel]): identical+=1 elif len(different)<50: different.append(rel) info={ "mysql":{"release":args.mysql_release,"files":len(mi),"root":str(mysql_root)}, "postgresql":{"release":args.postgres_release,"files":len(pi),"root":str(pg_root)}, "compare":{"common":len(common),"identical":identical,"different":len(common)-identical,"different_examples":different}, } class Handler(BaseHTTPRequestHandler): def send(self,data:bytes,ctype:str,status=200): self.send_response(status) self.send_header("Content-Type",ctype) self.send_header("Content-Length",str(len(data))) self.send_header("Cache-Control","no-cache") self.send_header("Access-Control-Allow-Origin","*") self.end_headers() self.wfile.write(data) def serve_file(self,path:Path,ctype:str): if not path.is_file(): self.send_error(404) return self.send(path.read_bytes(),ctype) def do_GET(self): path=urlparse(self.path).path if path in ("/","/index.html","/compare"): return self.serve_file(html,"text/html; charset=utf-8") if path=="/api/info": return self.send(json.dumps(info,ensure_ascii=False,indent=2).encode(),"application/json; charset=utf-8") if path=="/style.json": return self.serve_file(style,"application/json; charset=utf-8") if path in ("/assets/maplibre-gl.js","/assets/maplibre-gl.css"): fn=path.rsplit("/",1)[-1] ct="application/javascript; charset=utf-8" if fn.endswith(".js") else "text/css; charset=utf-8" return self.serve_file(assets/fn,ct) if path in ("/sprite.json","/sprite.png","/sprite@2x.json","/sprite@2x.png"): fn=path[1:] ct="application/json; charset=utf-8" if fn.endswith(".json") else "image/png" return self.serve_file(resources/fn,ct) for prefix,root in (("/mysql/",mysql_root),("/postgres/",pg_root)): if path.startswith(prefix) and path.endswith(".pbf"): rel=path[len(prefix):] parts=rel.split("/") if len(parts)!=3 or not all(x.isdigit() for x in [parts[0],parts[1],parts[2][:-4]]): self.send_error(404); return return self.serve_file(root/rel,"application/vnd.mapbox-vector-tile") self.send_error(404) def log_message(self,fmt,*args): pass print(json.dumps(info,ensure_ascii=False,indent=2)) print(f"Compare: http://{args.host}:{args.port}/") ThreadingHTTPServer((args.host,args.port),Handler).serve_forever() if __name__=="__main__": main()