"""HTTP-based P2P sync protocol for snippets.""" import http.server import json import socketserver import threading from datetime import datetime from typing import Any from urllib.request import urlopen from snip.db.database import Database class SyncRequestHandler(http.server.BaseHTTPRequestHandler): def do_GET(self): if self.path.startswith("/snippets"): since = self.headers.get("X-Since", "1970-01-01T00:00:00") snippets = self.server.db.list_snippets(limit=10000) snippets = [s for s in snippets if s["updated_at"] > since] self.send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps(snippets).encode()) else: self.send_response(404) self.end_headers() def do_POST(self): if self.path == "/snippets": content_length = int(self.headers["Content-Length"]) data = json.loads(self.rfile.read(content_length)) for snippet in data: self.server.db.import_snippet(snippet, strategy="duplicate") self.send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps({"status": "ok"}).encode()) else: self.send_response(404) self.end_headers() def log_message(self, format, *args): pass class SyncServer(socketserver.TCPServer): allow_reuse_address = True def __init__(self, port: int, db: Database): self.db = db super().__init__(("", port), SyncRequestHandler) class SyncProtocol: def __init__(self, db: Database, port: int = 8765): self.db = db self.port = port self.server = None self.server_thread = None def start_server(self): """Start the sync server in a background thread.""" self.server = SyncServer(self.port, self.db) self.server_thread = threading.Thread(target=self.server.serve_forever) self.server_thread.daemon = True self.server_thread.start() def stop_server(self): """Stop the sync server.""" if self.server: self.server.shutdown() self.server = None def sync_with_peer(self, host: str, port: int) -> int: """Sync snippets with a peer.""" snippets = [] synced = 0 try: with urlopen(f"http://{host}:{port}/snippets", timeout=30) as response: snippets = json.loads(response.read()) except Exception: pass for snippet in snippets: if "id" in snippet: del snippet["id"] self.db.import_snippet(snippet, strategy="skip") synced += 1 return synced def push_to_peer(self, host: str, port: int) -> int: """Push local snippets to a peer.""" snippets = self.db.export_all() pushed = 0 try: req = urllib.request.Request( f"http://{host}:{port}/snippets", data=json.dumps(snippets).encode(), headers={"Content-Type": "application/json"}, ) with urlopen(req, timeout=30) as response: if response.status == 200: pushed = len(snippets) except Exception: pass return pushed