Files
agentic-codebase-memory-man…/snip/sync/protocol.py

111 lines
3.4 KiB
Python

"""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