""" Background tasks for Org Social Host. """ import logging import time from datetime import timedelta import requests from django.conf import settings from django.utils import timezone from huey import crontab from huey.contrib.djhuey import db_periodic_task from .models import HostedFile logger = logging.getLogger(__name__) # Seconds to sleep between consecutive POST requests to the relay RELAY_SYNC_REQUEST_DELAY = 1.0 @db_periodic_task(crontab(hour="0", minute="0")) def cleanup_stale_files(): """ Clean up files that haven't been updated within the TTL period. Runs daily at midnight UTC. """ # Check if cleanup is enabled if not settings.ENABLE_CLEANUP: logger.info("Automatic cleanup is disabled. Skipping cleanup task.") return logger.info("Starting cleanup of stale files...") # Calculate cutoff date cutoff_date = timezone.now() - timedelta(days=settings.FILE_TTL_DAYS) # Find stale files stale_files = HostedFile.objects.filter(last_access__lt=cutoff_date) count = stale_files.count() if count == 0: logger.info("No stale files found.") return logger.info(f"Found {count} stale files to delete.") # Delete database records for hosted_file in stale_files: try: nickname = hosted_file.nickname hosted_file.delete() logger.info(f"Deleted hosted file record: {nickname}") except Exception as e: logger.error(f"Error deleting file {hosted_file.nickname}: {e}") logger.info(f"Cleanup completed. Deleted {count} stale files.") def _build_public_feed_url(nickname: str) -> str: """Build the public social.org URL for a given nickname using SITE_DOMAIN.""" domain = settings.SITE_DOMAIN scheme = "http" if domain.startswith("localhost") else "https" return f"{scheme}://{domain}/{nickname}/social.org" @db_periodic_task(crontab(minute="0")) def sync_feeds_to_relay(): """ Register all hosted feeds on the configured relay every hour. Disabled when RELAY_URL is not set. The relay handles deduplication, so this task simply re-announces all public feeds on every run. """ relay_url = settings.RELAY_URL if not relay_url: logger.info("RELAY_URL is not configured. Skipping relay sync task.") return feeds_endpoint = f"{relay_url}/feeds/" hosted_files = HostedFile.objects.filter( redirect_url__isnull=True, ).exclude(file_content="") total = hosted_files.count() if total == 0: logger.info("No hosted feeds to sync to relay.") return logger.info(f"Syncing {total} feed(s) to relay {feeds_endpoint}") succeeded = 0 failed = 0 for index, hosted_file in enumerate(hosted_files): feed_url = _build_public_feed_url(hosted_file.nickname) try: response = requests.post( feeds_endpoint, json={"feed": feed_url}, timeout=10, ) if response.status_code in (200, 201): succeeded += 1 else: failed += 1 logger.warning( f"Relay rejected feed {feed_url}: " f"HTTP {response.status_code} {response.text[:200]}" ) except requests.RequestException as exc: failed += 1 logger.warning(f"Failed to register feed {feed_url} on relay: {exc}") # Throttle: pause between requests, except after the final one. if index < total - 1: time.sleep(RELAY_SYNC_REQUEST_DELAY) logger.info( f"Relay sync completed. Succeeded: {succeeded}, Failed: {failed}, Total: {total}" )