A Python port of the Invisible Internet Project (I2P)
at main 92 lines 2.8 kB view raw
1"""SQLite-backed NetDB cache for persistent RouterInfo storage. 2 3Stores RouterInfo entries in ~/.i2p-python/netdb.sqlite3. Survives 4restarts so the router doesn't need to reseed every time. Uses WAL 5mode for concurrent read/write from maintenance tasks. 6 7Schema designed to support future reputation system columns. 8""" 9 10from __future__ import annotations 11 12import sqlite3 13import time 14from pathlib import Path 15 16 17class SqliteNetDB: 18 """SQLite-backed persistent NetDB cache.""" 19 20 def __init__(self, data_dir: str) -> None: 21 db_path = Path(data_dir).expanduser() / "netdb.sqlite3" 22 db_path.parent.mkdir(parents=True, exist_ok=True) 23 self._conn = sqlite3.connect(str(db_path)) 24 self._conn.execute("PRAGMA journal_mode=WAL") 25 self._conn.execute(""" 26 CREATE TABLE IF NOT EXISTS router_info ( 27 key BLOB PRIMARY KEY, 28 data BLOB NOT NULL, 29 received_at INTEGER NOT NULL, 30 expires_at INTEGER NOT NULL 31 ) 32 """) 33 self._conn.commit() 34 35 def store(self, key: bytes, data: bytes, ttl_ms: int = 86_400_000) -> None: 36 """Store or replace a RouterInfo entry. 37 38 Parameters 39 ---------- 40 key: 41 32-byte router identity hash. 42 data: 43 Raw RouterInfo bytes. 44 ttl_ms: 45 Time-to-live in milliseconds (default 24h). 46 """ 47 now = int(time.time() * 1000) 48 self._conn.execute( 49 "INSERT OR REPLACE INTO router_info (key, data, received_at, expires_at) " 50 "VALUES (?, ?, ?, ?)", 51 (key, data, now, now + ttl_ms), 52 ) 53 self._conn.commit() 54 55 def load_all(self) -> list[tuple[bytes, bytes]]: 56 """Load all non-expired entries. 57 58 Returns 59 ------- 60 list[tuple[bytes, bytes]] 61 List of (key, data) tuples. 62 """ 63 now = int(time.time() * 1000) 64 rows = self._conn.execute( 65 "SELECT key, data FROM router_info WHERE expires_at > ?", (now,) 66 ).fetchall() 67 return rows 68 69 def evict_expired(self) -> int: 70 """Delete expired entries. 71 72 Returns 73 ------- 74 int 75 Number of entries deleted. 76 """ 77 now = int(time.time() * 1000) 78 cur = self._conn.execute( 79 "DELETE FROM router_info WHERE expires_at <= ?", (now,) 80 ) 81 self._conn.commit() 82 return cur.rowcount 83 84 def count(self) -> int: 85 """Return total entry count (including expired).""" 86 return self._conn.execute( 87 "SELECT COUNT(*) FROM router_info" 88 ).fetchone()[0] 89 90 def close(self) -> None: 91 """Close the database connection.""" 92 self._conn.close()