Files
voyage/backend/server/run_periodic_sync.py
alex df8d1adf15 Rename AdventureLog to Voyage and add fork attribution
- Replace all AdventureLog references with Voyage across ~102 files
  (7 case variants: AdventureLog, adventurelog, Adventurelog, ADVENTURELOG,
  AdventUrelog, AdventureLOG, adventure-log, adventure_log)
- Rename brand, static, and documentation assets to use voyage naming
- Rename install_adventurelog.sh → install_voyage.sh
- Update README.md and voyage_overview.md to credit AdventureLog as
  the upstream project and Sean Morley as its original creator
2026-03-06 11:05:26 +00:00

106 lines
3.2 KiB
Python

#!/usr/bin/env python3
"""
Periodic sync runner for Voyage.
Runs sync_visited_regions management command every 60 seconds.
Managed by supervisord to ensure it inherits container environment variables.
"""
import os
import sys
import time
import logging
import signal
import threading
from datetime import datetime, timedelta
from pathlib import Path
# Setup Django
sys.path.insert(0, str(Path(__file__).parent))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'main.settings')
import django
django.setup()
from django.core.management import call_command
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
handlers=[logging.StreamHandler(sys.stdout)]
)
logger = logging.getLogger(__name__)
INTERVAL_SECONDS = 60
# Event used to signal shutdown from signal handlers
_stop_event = threading.Event()
def _seconds_until_next_midnight() -> float:
"""Return number of seconds until the next local midnight."""
now = datetime.now()
next_midnight = (now + timedelta(days=1)).replace(
hour=0, minute=0, second=0, microsecond=0
)
return (next_midnight - now).total_seconds()
def _handle_termination(signum, frame):
"""Signal handler for SIGTERM and SIGINT: request graceful shutdown."""
logger.info(f"Received signal {signum}; shutting down gracefully...")
_stop_event.set()
def run_sync():
"""Run the sync_visited_regions command."""
try:
logger.info("Running sync_visited_regions...")
call_command('sync_visited_regions')
logger.info("Sync completed successfully")
except Exception as e:
logger.error(f"Sync failed: {e}", exc_info=True)
def main():
"""Main loop - run sync every INTERVAL_SECONDS."""
logger.info(f"Starting periodic sync worker for midnight background jobs...")
# Install signal handlers so supervisord (or other process managers)
# can request a clean shutdown using SIGTERM/SIGINT.
signal.signal(signal.SIGTERM, _handle_termination)
signal.signal(signal.SIGINT, _handle_termination)
try:
while not _stop_event.is_set():
# Wait until the next local midnight (or until shutdown)
wait_seconds = _seconds_until_next_midnight()
hours = wait_seconds / 3600.0
logger.info(
f"Next sync scheduled in {wait_seconds:.0f}s (~{hours:.2f}h) at UTC midnight"
)
# Sleep until midnight or until stop event is set
if _stop_event.wait(wait_seconds):
break
# It's midnight (or we woke up), run the sync once
run_sync()
# After running at midnight, loop continues to compute next midnight
except Exception:
logger.exception("Unexpected error in periodic sync loop")
finally:
logger.info("Periodic sync worker exiting")
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
# Fallback in case the signal is delivered as KeyboardInterrupt
logger.info("KeyboardInterrupt received — exiting")
_stop_event.set()
except SystemExit:
logger.info("SystemExit received — exiting")
finally:
logger.info("run_periodic_sync terminated")