mirror of
https://github.com/sonr-io/common.git
synced 2026-01-11 20:08:57 +00:00
241 lines
7.9 KiB
Go
241 lines
7.9 KiB
Go
// Package ipfs provides a high-level interface for interacting with an IPFS node.
|
|
package ipfs
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/ipfs/boxo/files"
|
|
"github.com/ipfs/boxo/path"
|
|
"github.com/ipfs/kubo/client/rpc"
|
|
iface "github.com/ipfs/kubo/core/coreiface"
|
|
"github.com/ipfs/kubo/core/coreiface/options"
|
|
)
|
|
|
|
// NodeStatus contains information about an IPFS node's status and connectivity.
|
|
type NodeStatus struct {
|
|
// PeerID is the unique identifier of the IPFS node
|
|
PeerID string
|
|
// Version is the version of the IPFS implementation
|
|
Version string
|
|
// PeerType describes the type of IPFS node (e.g., "kubo")
|
|
PeerType string
|
|
// ConnectedPeers is the number of peers currently connected to this node
|
|
ConnectedPeers int
|
|
}
|
|
|
|
// IPFSClient provides a high-level interface for interacting with an IPFS node.
|
|
// It abstracts the complexity of the underlying Kubo RPC API and provides
|
|
// convenient methods for common IPFS operations.
|
|
type IPFSClient interface {
|
|
// Add stores raw byte data in IPFS and returns the resulting CID.
|
|
// The data is stored as a single file node in the IPFS DAG.
|
|
Add(data []byte) (string, error)
|
|
|
|
// AddFile stores a File with metadata in IPFS and returns the resulting CID.
|
|
// The file's name and content are preserved in the IPFS node.
|
|
AddFile(file File) (string, error)
|
|
|
|
// AddFolder stores a directory structure in IPFS and returns the root CID.
|
|
// The folder and its contents are stored as a directory node in IPFS.
|
|
AddFolder(folder Folder) (string, error)
|
|
|
|
// Exists checks if content with the given CID exists in the IPFS network.
|
|
// It queries the local node's blockstore to verify availability.
|
|
Exists(cid string) (bool, error)
|
|
|
|
// Get retrieves the content of a file from IPFS using its CID.
|
|
// Returns the raw bytes of the file content.
|
|
Get(cid string) ([]byte, error)
|
|
|
|
// IsPinned checks if content is pinned on the local IPFS node using IPNS resolution.
|
|
// Returns true if the IPNS name can be successfully resolved.
|
|
IsPinned(ipns string) (bool, error)
|
|
|
|
// Ls lists the contents of a directory in IPFS using its CID.
|
|
// Returns the names of all entries in the directory.
|
|
Ls(cid string) ([]string, error)
|
|
|
|
// Pin ensures that content with the given CID is retained in the local IPFS node.
|
|
// The content will not be garbage collected and can be assigned a human-readable name.
|
|
Pin(cid string, name string) error
|
|
|
|
// Unpin removes the pin for content with the given CID.
|
|
// The content may be garbage collected if not pinned elsewhere.
|
|
Unpin(cid string) error
|
|
|
|
// NodeStatus returns information about the IPFS node including peer ID, version, and connectivity.
|
|
// Returns node identity, version information, and connected peer count.
|
|
NodeStatus() (*NodeStatus, error)
|
|
}
|
|
|
|
// ipfsClient implements the Client interface using the Kubo RPC API.
|
|
// It maintains a connection to a local IPFS node via HTTP API.
|
|
type ipfsClient struct {
|
|
api *rpc.HttpApi
|
|
}
|
|
|
|
// GetClient creates a new IPFS client connected to a local IPFS node.
|
|
// It establishes a connection to the local Kubo RPC API endpoint.
|
|
// Returns an error if the local IPFS node is not available or accessible.
|
|
func GetClient() (IPFSClient, error) {
|
|
api, err := rpc.NewLocalApi()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &ipfsClient{api: api}, nil
|
|
}
|
|
|
|
// Add stores raw byte data in IPFS and returns the resulting CID string.
|
|
// The data is wrapped in a BytesFile and added to the UnixFS layer.
|
|
func (c *ipfsClient) Add(data []byte) (string, error) {
|
|
file := files.NewBytesFile(data)
|
|
cidFile, err := c.api.Unixfs().Add(context.Background(), file)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return cidFile.String(), nil
|
|
}
|
|
|
|
// Get retrieves content from IPFS using the provided CID string.
|
|
// It resolves the CID path, fetches the content, and returns the raw bytes.
|
|
// Returns an error if the CID is invalid or the content cannot be retrieved.
|
|
func (c *ipfsClient) Get(cid string) ([]byte, error) {
|
|
p, err := path.NewPath(cid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
node, err := c.api.Unixfs().Get(context.Background(), p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
file, ok := node.(files.File)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unexpected node type: %T", node)
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
if _, err := io.Copy(buf, file); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// IsPinned checks if an IPNS name can be resolved, indicating the content is available.
|
|
// This method uses IPNS resolution rather than direct pin checking.
|
|
// Returns false if the IPNS name cannot be resolved.
|
|
func (c *ipfsClient) IsPinned(ipns string) (bool, error) {
|
|
_, err := c.api.Name().Resolve(context.Background(), ipns)
|
|
if err != nil {
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// Exists verifies if content with the given CID exists in the IPFS network.
|
|
// It checks the block statistics to determine availability.
|
|
// Returns false if the CID is invalid or the content is not found.
|
|
func (c *ipfsClient) Exists(cid string) (bool, error) {
|
|
p, err := path.NewPath(cid)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
_, err = c.api.Block().Stat(context.Background(), p)
|
|
if err != nil {
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// Pin adds content to the pin set, preventing it from being garbage collected.
|
|
// The content is identified by its CID and can be assigned a descriptive name.
|
|
// Returns an error if the CID is invalid or pinning fails.
|
|
func (c *ipfsClient) Pin(cid string, name string) error {
|
|
p, err := path.NewPath(cid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.api.Pin().Add(context.Background(), p, options.Pin.Name(name))
|
|
}
|
|
|
|
// Unpin removes content from the pin set, allowing it to be garbage collected.
|
|
// The content is identified by its CID string.
|
|
// Returns an error if the CID is invalid or unpinning fails.
|
|
func (c *ipfsClient) Unpin(cid string) error {
|
|
p, err := path.NewPath(cid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.api.Pin().Rm(context.Background(), p)
|
|
}
|
|
|
|
// Ls lists the contents of an IPFS directory using its CID.
|
|
// It returns the names of all entries in the directory.
|
|
// The listing is performed asynchronously using channels to handle large directories efficiently.
|
|
func (c *ipfsClient) Ls(cid string) ([]string, error) {
|
|
p, err := path.NewPath(cid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dirChan := make(chan iface.DirEntry)
|
|
files := make([]string, 0)
|
|
lsErr := make(chan error, 1)
|
|
go func() {
|
|
lsErr <- c.api.Unixfs().Ls(context.Background(), p, dirChan)
|
|
}()
|
|
for dirEnt := range dirChan {
|
|
files = append(files, dirEnt.Name)
|
|
}
|
|
if err := <-lsErr; err != nil {
|
|
return nil, err
|
|
}
|
|
return files, nil
|
|
}
|
|
|
|
// AddFile stores a File with its metadata in IPFS and returns the resulting CID.
|
|
// This method preserves the file's name and content structure when adding to IPFS.
|
|
// It's implemented as a method on the client to maintain consistency with the Client interface.
|
|
func (c *ipfsClient) AddFile(file File) (string, error) {
|
|
cidFile, err := c.api.Unixfs().Add(context.Background(), file)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return cidFile.String(), nil
|
|
}
|
|
|
|
// NodeStatus retrieves status information about the IPFS node including peer ID, version, and connectivity.
|
|
// It queries the node's identity, version, and connected peers to provide comprehensive status information.
|
|
func (c *ipfsClient) NodeStatus() (*NodeStatus, error) {
|
|
ctx := context.Background()
|
|
|
|
// Get node ID information using Key().Self() to get the node's own peer ID
|
|
nodeKey, err := c.api.Key().Self(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get node ID: %w", err)
|
|
}
|
|
|
|
// Get connected peers count
|
|
swarmPeers, err := c.api.Swarm().Peers(ctx)
|
|
connectedPeers := 0
|
|
if err == nil {
|
|
connectedPeers = len(swarmPeers)
|
|
}
|
|
|
|
// Since we successfully got the key information, we know the node is responsive
|
|
versionStr := "kubo-responsive"
|
|
if connectedPeers > 0 {
|
|
versionStr = "kubo-connected"
|
|
}
|
|
|
|
return &NodeStatus{
|
|
PeerID: nodeKey.Name(),
|
|
Version: versionStr,
|
|
PeerType: "kubo",
|
|
ConnectedPeers: connectedPeers,
|
|
}, nil
|
|
}
|