# retoor <retoor@molodetz.nl>
from __future__ import annotations
import subprocess
import sys
from devplacepy.config import XMLRPC_BIND, XMLRPC_PORT
from devplacepy.services.base import BaseService
XMLRPC_INTERVAL_SECONDS = 15
SERVER_MODULE = "devplacepy.services.xmlrpc.server"
TERMINATE_TIMEOUT_SECONDS = 10
class XmlrpcService(BaseService):
title = "XML-RPC Bridge"
description = (
"Forking XML-RPC server that exposes every documented REST endpoint as an "
"XML-RPC method. Reachable at /xmlrpc through the app and nginx."
)
default_enabled = True
min_interval = 5
def __init__(self) -> None:
super().__init__("xmlrpc", interval_seconds=XMLRPC_INTERVAL_SECONDS)
self._process: subprocess.Popen | None = None
def _alive(self) -> bool:
return self._process is not None and self._process.poll() is None
def _spawn(self) -> None:
self._process = subprocess.Popen(
[sys.executable, "-m", SERVER_MODULE],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
self.log(
f"Forking XML-RPC server started (pid {self._process.pid}) on "
f"{XMLRPC_BIND}:{XMLRPC_PORT}"
)
def _terminate(self) -> None:
if not self._alive():
self._process = None
return
self._process.terminate()
try:
self._process.wait(timeout=TERMINATE_TIMEOUT_SECONDS)
except subprocess.TimeoutExpired:
self._process.kill()
self._process.wait()
self.log("Forking XML-RPC server stopped")
self._process = None
async def on_enable(self) -> None:
if not self._alive():
self._spawn()
async def on_disable(self) -> None:
self._terminate()
async def run_once(self) -> None:
if self._alive():
self.log(f"XML-RPC server healthy (pid {self._process.pid})")
return
self.log("XML-RPC server not running, starting it")
self._spawn()
def collect_metrics(self) -> dict:
return {
"running": self._alive(),
"pid": self._process.pid if self._alive() else None,
"bind": f"{XMLRPC_BIND}:{XMLRPC_PORT}",
}
__all__ = ["XmlrpcService"]