#!/usr/bin/env python3 import os import tempfile import threading from flask import Flask, jsonify, request, abort, render_template_string app = Flask(__name__) # --- Configuration --- VALUE_FILE = os.environ.get("VALUE_FILE", "value.txt") POLL_INTERVAL_MS = int(os.environ.get("POLL_INTERVAL_MS", "20000")) # client refresh rate _write_lock = threading.Lock() # --- Helpers --- def _clamp_to_range(val: int) -> int: return max(0, min(99, int(val))) def _ensure_file_exists(): """Create the value file with default '0' if missing or invalid.""" if not os.path.exists(VALUE_FILE): _atomic_write("0") return try: _ = _read_value() except Exception: _atomic_write("0") def _read_value() -> int: """Read the current numeric value from disk.""" with open(VALUE_FILE, "r", encoding="utf-8") as f: raw = f.read().strip() # Allow blank/new files to recover to 0 if raw == "": return 0 n = int(raw) return _clamp_to_range(n) def _atomic_write(text: str): """Atomically write text to VALUE_FILE.""" dir_name = os.path.dirname(os.path.abspath(VALUE_FILE)) or "." os.makedirs(dir_name, exist_ok=True) fd, tmp_path = tempfile.mkstemp(prefix=".value-", dir=dir_name, text=True) try: with os.fdopen(fd, "w", encoding="utf-8") as tmp: tmp.write(text) tmp.flush() os.fsync(tmp.fileno()) os.replace(tmp_path, VALUE_FILE) # atomic on POSIX/Windows finally: # If replace succeeded, tmp_path no longer exists; ignore errors. try: if os.path.exists(tmp_path): os.remove(tmp_path) except Exception: pass def _get_mtime() -> float: try: return os.path.getmtime(VALUE_FILE) except FileNotFoundError: return 0.0 # --- Routes --- @app.route("/", methods=["GET"]) def index(): _ensure_file_exists() val = _read_value() mtime = _get_mtime() # Single-file template for convenience html = """
{{value_file}}. Updates live if the file changes.