Files
CM95/web/mysql_postgresql_compare_server.py
2026-10-02 18:03:26 +08:00

106 lines
4.5 KiB
Python
Executable File

#!/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.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()