diff options
author | Nguyễn Gia Phong <cnx@loang.net> | 2025-06-07 17:41:29 +0900 |
---|---|---|
committer | Nguyễn Gia Phong <cnx@loang.net> | 2025-06-07 17:41:29 +0900 |
commit | ac8091aadcd8933707709473c4d7c40299e56e6b (patch) | |
tree | cb1b3a671b591ad282a67ee8bfa592404d55f9af | |
parent | c540eea9405223860a8854b0b8adc16dcdfa707d (diff) | |
download | scadere-ac8091aadcd8933707709473c4d7c40299e56e6b.tar.gz |
Cache XML and file I/O calls
-rw-r--r-- | src/scadere/listen.py | 52 |
1 files changed, 34 insertions, 18 deletions
diff --git a/src/scadere/listen.py b/src/scadere/listen.py index add4b05..7c683e5 100644 --- a/src/scadere/listen.py +++ b/src/scadere/listen.py @@ -20,7 +20,7 @@ from argparse import ArgumentParser from asyncio import run, start_server from base64 import urlsafe_b64decode as from_base64 from datetime import datetime, timezone -from functools import partial +from functools import lru_cache, partial from http import HTTPStatus from operator import call from pathlib import Path @@ -29,7 +29,7 @@ from typing import assert_never from urllib.parse import parse_qs, urljoin, urlsplit from xml.etree.ElementTree import (Element as xml_element, SubElement as xml_subelement, - indent, tostringlist as strings_from_xml) + indent, tostring as string_from_xml) from sys import argv from . import __version__, GNUHelpFormatter, NetLoc @@ -38,6 +38,12 @@ from .check import base64_from_str __all__ = ['main'] +@lru_cache +def read_text(file, time): + """Read the given text file if it has been updated.""" + return file.read_text() + + def datetime_from_str(string, unavailable_ok=False): """Parse datetime from string in ISO 8601 format.""" if string == 'N/A' and unavailable_ok: @@ -151,13 +157,13 @@ def is_subdomain(subject, objects): for obj_parts in map(split_domain, objects)) -def feed(base_url, filename, certificates, domains): +def feed(base_url, name, mtime, certificates, domains): """Construct an Atom feed based on the given information.""" return ('feed', {'xmlns': 'http://www.w3.org/2005/Atom'}, ('id', base_url), ('link', {'rel': 'self', 'href': base_url}), - ('title', filename), - ('updated', datetime.now(timezone.utc).isoformat()), + ('title', name), + ('updated', mtime), ('generator', {'uri': 'https://trong.loang.net/scadere/about', 'version': __version__}, @@ -208,13 +214,24 @@ def xml(tree, parent=None): return elem +@lru_cache +def unparsed_feed(*args): + """Cache Atom feed.""" + return string_from_xml(xml(feed(*args)), 'unicode', + xml_declaration=True, default_namespace=None) + + +@lru_cache +def unparsed_page(*args): + """Cache XHTML page.""" + return string_from_xml(xml(page(*args)), 'unicode', + xml_declaration=True, default_namespace=None) + + async def write_xml(writer, http_version, application, func, *args): """Write given document as XML.""" try: - content = tuple(map(str.encode, - strings_from_xml(xml(func(*args)), 'unicode', - xml_declaration=True, - default_namespace=None))) + content = func(*args).encode() except Exception: # pragma: no cover await describe_status(writer, HTTPStatus.INTERNAL_SERVER_ERROR, http_version) @@ -222,11 +239,9 @@ async def write_xml(writer, http_version, application, func, *args): else: await write_status(writer, http_version, HTTPStatus.OK) await write_content_type(writer, f'application/{application}+xml') - length = sum(map(len, content)) - writer.write(f'Content-Length: {length}\r\n\r\n'.encode()) - for part in content: - writer.write(part) - await writer.drain() + writer.write(f'Content-Length: {len(content)}\r\n\r\n'.encode()) + writer.write(content) + await writer.drain() async def handle(certs, base_url, reader, writer): @@ -254,8 +269,9 @@ async def handle(certs, base_url, reader, writer): return try: + mtime = datetime.fromtimestamp(certs.stat().st_mtime, timezone.utc) summaries = tuple(map(parse_summary, - certs.read_text().splitlines())) + read_text(certs, mtime).splitlines())) paths = tuple(urlsplit(urljoin(base_url, path(*s[-4:]))).path for s in summaries) lookup = dict(map(tuple, zip(paths, summaries))) @@ -267,10 +283,10 @@ async def handle(certs, base_url, reader, writer): raise if url_parts.path == urlsplit(base_url).path: # Atom feed - await write_xml(writer, http_version, 'atom', feed, - base_url, certs.name, lookup.values(), domains) + await write_xml(writer, http_version, 'atom', unparsed_feed, + base_url, certs.name, mtime, summaries, domains) elif url_parts.path in lookup: # accessible Atom entry's link/ID - await write_xml(writer, http_version, 'xhtml', page, + await write_xml(writer, http_version, 'xhtml', unparsed_page, lookup.get(url_parts.path)) else: await describe_status(writer, HTTPStatus.NOT_FOUND, http_version) |