aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNguyễn Gia Phong <cnx@loang.net>2025-06-07 17:41:29 +0900
committerNguyễn Gia Phong <cnx@loang.net>2025-06-07 17:41:29 +0900
commitac8091aadcd8933707709473c4d7c40299e56e6b (patch)
treecb1b3a671b591ad282a67ee8bfa592404d55f9af
parentc540eea9405223860a8854b0b8adc16dcdfa707d (diff)
downloadscadere-ac8091aadcd8933707709473c4d7c40299e56e6b.tar.gz
Cache XML and file I/O calls
-rw-r--r--src/scadere/listen.py52
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)