Extract all magic numbers into share.config

Unified config file with [server] (user-facing: port, dir, ttl, lang,
etc.) and [internal] (constants: timeouts, buffer sizes, cert params,
UI timings). Replaces both DEFAULTS dict and hardcoded constants.
JS constants (toast_ms, progress_hide_ms) passed through CFG injection.
Old share.conf no longer used.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 20:52:57 +04:00
co-authored by Claude Opus 4.6
parent 082a80a6c5
commit 9cc4693f39
3 changed files with 60 additions and 26 deletions
+19
View File
@@ -0,0 +1,19 @@
[server]
port = 3001
dir = ~/Downloads/shared
ttl = 7
san =
lang = ru
refresh = 5
per_page = 10,25,50,100
[internal]
secs_per_day = 86400
cleanup_interval = 3600
ssl_handshake_timeout = 5
sniff_size = 8192
chunk_size = 65536
cert_days = 3650
cert_key_bits = 2048
toast_ms = 2500
progress_hide_ms = 1200
+37 -24
View File
@@ -24,7 +24,7 @@ def cleanup_loop(upload_dir: Path, max_age: int):
if max_age <= 0:
return
while True:
time.sleep(3600)
time.sleep(CLEANUP_INTERVAL)
now = time.time()
try:
entries = list(upload_dir.iterdir())
@@ -68,9 +68,9 @@ def ensure_cert(cert_dir: Path, extra_ips: list[str] | None = None) -> tuple[Pat
san = ",".join([f"IP:{ip}" for ip in sorted(ips)] + ["DNS:localhost"])
subprocess.run([
"openssl", "req", "-x509", "-newkey", "rsa:2048",
"openssl", "req", "-x509", "-newkey", f"rsa:{CERT_KEY_BITS}",
"-keyout", str(key), "-out", str(cert),
"-days", "3650", "-nodes",
"-days", str(CERT_DAYS), "-nodes",
"-subj", "/CN=Share",
"-addext", f"subjectAltName={san}",
], check=True, capture_output=True)
@@ -80,6 +80,32 @@ def ensure_cert(cert_dir: Path, extra_ips: list[str] | None = None) -> tuple[Pat
BASE_DIR = Path(__file__).resolve().parent
# ── Config ──
_cp = configparser.ConfigParser()
_cp.read_dict({
"server": {
"port": "3001", "dir": str(Path.home() / "Downloads" / "shared"),
"ttl": "7", "san": "", "lang": "ru", "refresh": "5",
"per_page": "10,25,50,100",
},
"internal": {
"secs_per_day": "86400", "cleanup_interval": "3600",
"ssl_handshake_timeout": "5", "sniff_size": "8192",
"chunk_size": "65536", "cert_days": "3650", "cert_key_bits": "2048",
"toast_ms": "2500", "progress_hide_ms": "1200",
},
})
_cp.read(BASE_DIR / "share.config")
SECS_PER_DAY = _cp.getint("internal", "secs_per_day")
CLEANUP_INTERVAL = _cp.getint("internal", "cleanup_interval")
SSL_HANDSHAKE_TIMEOUT = _cp.getint("internal", "ssl_handshake_timeout")
SNIFF_SIZE = _cp.getint("internal", "sniff_size")
CHUNK_SIZE = _cp.getint("internal", "chunk_size")
CERT_DAYS = _cp.getint("internal", "cert_days")
CERT_KEY_BITS = _cp.getint("internal", "cert_key_bits")
def _file_type(path: Path) -> str:
ct = mimetypes.guess_type(path.name)[0] or ''
if ct.startswith('image/'):
@@ -88,7 +114,7 @@ def _file_type(path: Path) -> str:
return 'text'
try:
with path.open('rb') as f:
return 'text' if b'\x00' not in f.read(8192) else 'other'
return 'text' if b'\x00' not in f.read(SNIFF_SIZE) else 'other'
except OSError:
return 'other'
@@ -106,7 +132,7 @@ class ShareServer(http.server.ThreadingHTTPServer):
def get_request(self):
client, addr = self.socket.accept()
if self.ssl_ctx:
client.settimeout(5)
client.settimeout(SSL_HANDSHAKE_TIMEOUT)
try:
client = self.ssl_ctx.wrap_socket(client, server_side=True)
except (ssl.SSLError, OSError):
@@ -195,7 +221,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
self.send_header("Connection", "close")
self.end_headers()
with safe.open("rb") as f:
while chunk := f.read(65536):
while chunk := f.read(CHUNK_SIZE):
self.wfile.write(chunk)
def _route_upload(self, _prefix):
@@ -252,24 +278,9 @@ class Handler(http.server.BaseHTTPRequestHandler):
# ── Main ──
DEFAULTS = {
"port": "3001",
"dir": str(Path.home() / "Downloads" / "shared"),
"ttl": "7",
"san": "",
"lang": "ru",
"refresh": "5",
"per_page": "10,25,50,100",
}
def load_config() -> dict:
"""Load config: defaults ← config file ← CLI args."""
cfg_path = BASE_DIR / "share.conf"
cp = configparser.ConfigParser()
cp.read_dict({"share": DEFAULTS})
cp.read(cfg_path)
conf = dict(cp["share"])
"""Load config: share.config defaults ← CLI args."""
conf = dict(_cp["server"])
p = argparse.ArgumentParser(description="Share — local file sharing server")
p.add_argument("-p", "--port", type=int)
@@ -292,7 +303,7 @@ def main():
port = int(conf["port"])
upload_dir = Path(conf["dir"]).expanduser().resolve()
max_age = int(conf["ttl"]) * 86400
max_age = int(conf["ttl"]) * SECS_PER_DAY
san_list = conf["san"].split() if conf["san"] else None
upload_dir.mkdir(parents=True, exist_ok=True)
@@ -304,6 +315,8 @@ def main():
"lang": conf["lang"],
"refresh": int(conf["refresh"]),
"perPage": [int(x) for x in conf["per_page"].split(",")],
"toastMs": _cp.getint("internal", "toast_ms"),
"progressHideMs": _cp.getint("internal", "progress_hide_ms"),
})
cfg_tag = f"<script>const CFG={cfg_json}</script>".encode()
html = (BASE_DIR / "static" / "index.html").read_bytes()
+4 -2
View File
@@ -194,6 +194,8 @@ const _cfg = typeof CFG !== 'undefined' ? CFG : {};
const defaultLang = _cfg.lang || 'ru';
const REFRESH_MS = (_cfg.refresh || 5) * 1000;
const PER_PAGE_OPTS = _cfg.perPage || [10, 25, 50, 100];
const TOAST_MS = _cfg.toastMs || 2500;
const PROGRESS_HIDE_MS = _cfg.progressHideMs || 1200;
/* ── i18n ── */
@@ -266,7 +268,7 @@ function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add('show');
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toastEl.classList.remove('show'), 2500);
toastTimer = setTimeout(() => toastEl.classList.remove('show'), TOAST_MS);
}
function fmtSize(b) {
@@ -371,7 +373,7 @@ function upload(fileList) {
xhr.onload = () => {
done();
pfill.style.width = '100%';
setTimeout(() => { $('progress').style.display = 'none'; pfill.style.width = '0' }, 1200);
setTimeout(() => { $('progress').style.display = 'none'; pfill.style.width = '0' }, PROGRESS_HIDE_MS);
if (xhr.status === 200) {
const r = JSON.parse(xhr.responseText);
toast(t().uploaded(r.count));