#!/usr/bin/env python3 """Shared low-level helpers for the mirror (no circular deps).""" import os, hashlib, urllib.parse, threading, queue BASE_HOST = "www.mitsuthailand.com" BASE_URL = f"https://{BASE_HOST}" MEDIA_HOST = "media.yellowpages.co.th" YP_HOST = "www.yellowpages.co.th" OUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "site") # ---------------- asset path mapping ---------------- def url_path_to_rel(parsed): """Same-host (mitsuthailand.com) asset -> site-relative path mirroring the original site structure (e.g. /sites/storage/... -> sites/storage/...). Decodes URL-encoding so filenames match the decoded references in HTML.""" path = urllib.parse.unquote(parsed.path).lstrip("/") parts = [seg for seg in path.split("/") if seg not in ("", "..", ".")] if not parts: parts = ["index"] name = parts[-1] if parsed.query: q = hashlib.md5(parsed.query.encode()).hexdigest()[:8] stem, dot, ext = name.rpartition(".") name = f"{stem}_{q}{dot}{ext}" if dot else f"{name}_{q}" return os.path.join(*parts) def external_url_to_rel(parsed): path = urllib.parse.unquote(parsed.path).lstrip("/") parts = [seg for seg in path.split("/") if seg not in ("", "..", ".")] if not parts: parts = ["index"] name = parts[-1] if parsed.query: q = hashlib.md5(parsed.query.encode()).hexdigest()[:8] stem, dot, ext = name.rpartition(".") name = f"{stem}_{q}{dot}{ext}" if dot else f"{name}_{q}" return os.path.join("assets", "external", *parts[:-1], name) def assets_rel_for(url): parsed = urllib.parse.urlparse(url) host = (parsed.hostname or "").lower() if host == BASE_HOST or parsed.netloc == "" or parsed.netloc.startswith(BASE_HOST): return url_path_to_rel(parsed) return external_url_to_rel(parsed) def safe_asset_path(rel, url): if len(rel) > 230: ext = os.path.splitext(rel)[1] rel = os.path.join("assets", "misc", hashlib.sha1(url.encode()).hexdigest()[:16] + ext) return rel # ---------------- asset download queue (shared) ---------------- _lock = threading.Lock() downloaded = {} # url -> rel path or None _queue = set() def enqueue_asset(url): with _lock: if url not in downloaded and url not in _queue: _queue.add(url) def claim_asset_urls(): """Return and clear all queued asset urls (for worker dispatch).""" with _lock: items = list(_queue) _queue.clear() return items def mark_asset_result(url, rel): with _lock: downloaded[url] = rel def is_downloaded(url): with _lock: return url in downloaded def pending_count(): with _lock: return len(_queue) def canonical_page_path(url): """Return the canonical decoded site-root-relative path for a page URL. Collapses encoded (%20 etc.) and decoded variants into one path.""" parsed = urllib.parse.urlparse(url) path = urllib.parse.unquote(parsed.path).rstrip("/") if path == "": return "/" return path