import os import time import json import sqlite3 import threading import random from pathlib import Path import sys try: from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler WATCHDOG_AVAILABLE = True except ImportError: WATCHDOG_AVAILABLE = False PROBLEMS_DIR = Path(__file__).parent / 'problems' DB_PATH = Path(__file__).parent / 'problems.sqlite3' class ProblemScannerThread(threading.Thread): def __init__(self, scan_interval=2): super().__init__(daemon=True) self.scan_interval = scan_interval self.last_state = {} self.observer = None def create_table(self, conn): c = conn.cursor() c.execute('PRAGMA journal_mode=WAL;') c.execute('''CREATE TABLE IF NOT EXISTS problems ( id INTEGER PRIMARY KEY AUTOINCREMENT, folder TEXT, description TEXT, test_code TEXT )''') conn.commit() def scan(self): problems = [] for folder in PROBLEMS_DIR.iterdir(): if folder.is_dir(): # Dynamically find manifest file (manifest.json or manifets.json) manifest_path = None for candidate in ["manifest.json", "manifets.json"]: candidate_path = folder / candidate if candidate_path.exists(): manifest_path = candidate_path break desc_path = folder / 'description.md' test_path = folder / 'test.py' if manifest_path and test_path.exists(): with open(desc_path, 'r') as f: description = f.read() with open(test_path, 'r') as f: test_code = f.read() problems.append({ 'folder': folder.name, 'description': description, 'test_code': test_code }) return problems def update_db(self, problems, retries=5): for attempt in range(retries): try: conn = sqlite3.connect(DB_PATH, timeout=2) c = conn.cursor() c.execute('PRAGMA journal_mode=WAL;') c.execute('DELETE FROM problems') for p in problems: c.execute('INSERT INTO problems (folder, description, test_code) VALUES (?, ?, ?)', (p['folder'], p['description'], p['test_code'])) conn.commit() conn.close() return except sqlite3.OperationalError as e: if 'locked' in str(e): time.sleep(0.2 + random.random() * 0.3) else: raise print('Failed to update problems DB after several retries due to lock.') def rescan_and_update(self): problems = self.scan() self.update_db(problems) def run(self): # Initial scan and table creation conn = sqlite3.connect(DB_PATH) self.create_table(conn) conn.close() self.rescan_and_update() if WATCHDOG_AVAILABLE: class Handler(FileSystemEventHandler): def __init__(self, scanner): self.scanner = scanner def on_any_event(self, event): self.scanner.rescan_and_update() event_handler = Handler(self) self.observer = Observer() self.observer.schedule(event_handler, str(PROBLEMS_DIR), recursive=True) self.observer.start() try: while True: time.sleep(1) finally: self.observer.stop() self.observer.join() else: # Fallback: poll every scan_interval seconds while True: time.sleep(self.scan_interval) self.rescan_and_update() def start_problem_scanner(): scanner = ProblemScannerThread() scanner.start() return scanner