- Add IPFS container to docker-compose - Add ipfshttpclient dependency - Add ipfs_client.py module for IPFS operations - Add ipfs_cid field to Asset model and database schema - Pin content on L2 IPFS when assets are published/registered L2 now stores content on its own IPFS node, enabling federation - content remains available even if L1 goes down. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
118 lines
2.6 KiB
Python
118 lines
2.6 KiB
Python
# art-activity-pub/ipfs_client.py
|
|
"""
|
|
IPFS client for Art DAG L2 server.
|
|
|
|
Provides functions to fetch and pin content from IPFS.
|
|
L2 uses IPFS to retrieve content from the federated network.
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
from typing import Optional
|
|
|
|
import ipfshttpclient
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# IPFS API multiaddr - default to local, docker uses /dns/ipfs/tcp/5001
|
|
IPFS_API = os.getenv("IPFS_API", "/ip4/127.0.0.1/tcp/5001")
|
|
|
|
# Connection timeout in seconds
|
|
IPFS_TIMEOUT = int(os.getenv("IPFS_TIMEOUT", "60"))
|
|
|
|
|
|
def get_client():
|
|
"""Get an IPFS client connection."""
|
|
return ipfshttpclient.connect(IPFS_API, timeout=IPFS_TIMEOUT)
|
|
|
|
|
|
def get_bytes(cid: str) -> Optional[bytes]:
|
|
"""
|
|
Retrieve content from IPFS by CID.
|
|
|
|
Args:
|
|
cid: IPFS CID to retrieve
|
|
|
|
Returns:
|
|
Content as bytes or None on failure
|
|
"""
|
|
try:
|
|
with get_client() as client:
|
|
data = client.cat(cid)
|
|
logger.info(f"Retrieved from IPFS: {cid} ({len(data)} bytes)")
|
|
return data
|
|
except Exception as e:
|
|
logger.error(f"Failed to get from IPFS: {e}")
|
|
return None
|
|
|
|
|
|
def pin(cid: str) -> bool:
|
|
"""
|
|
Pin a CID on this node.
|
|
|
|
Args:
|
|
cid: IPFS CID to pin
|
|
|
|
Returns:
|
|
True on success, False on failure
|
|
"""
|
|
try:
|
|
with get_client() as client:
|
|
client.pin.add(cid)
|
|
logger.info(f"Pinned on IPFS: {cid}")
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Failed to pin on IPFS: {e}")
|
|
return False
|
|
|
|
|
|
def unpin(cid: str) -> bool:
|
|
"""
|
|
Unpin a CID from this node.
|
|
|
|
Args:
|
|
cid: IPFS CID to unpin
|
|
|
|
Returns:
|
|
True on success, False on failure
|
|
"""
|
|
try:
|
|
with get_client() as client:
|
|
client.pin.rm(cid)
|
|
logger.info(f"Unpinned from IPFS: {cid}")
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Failed to unpin from IPFS: {e}")
|
|
return False
|
|
|
|
|
|
def is_available() -> bool:
|
|
"""
|
|
Check if IPFS daemon is available.
|
|
|
|
Returns:
|
|
True if IPFS is available, False otherwise
|
|
"""
|
|
try:
|
|
with get_client() as client:
|
|
client.id()
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def get_node_id() -> Optional[str]:
|
|
"""
|
|
Get this IPFS node's peer ID.
|
|
|
|
Returns:
|
|
Peer ID string or None on failure
|
|
"""
|
|
try:
|
|
with get_client() as client:
|
|
info = client.id()
|
|
return info.get("ID")
|
|
except Exception as e:
|
|
logger.error(f"Failed to get node ID: {e}")
|
|
return None
|