191 lines
4.6 KiB
Go
191 lines
4.6 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/libp2p/go-libp2p/core/host"
|
|
"github.com/libp2p/go-libp2p/core/peer"
|
|
"github.com/libp2p/go-libp2p/core/protocol"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// Client provides distributed storage client functionality
|
|
type Client struct {
|
|
host host.Host
|
|
logger *zap.Logger
|
|
namespace string
|
|
}
|
|
|
|
// NewClient creates a new storage client
|
|
func NewClient(h host.Host, namespace string, logger *zap.Logger) *Client {
|
|
return &Client{
|
|
host: h,
|
|
logger: logger,
|
|
namespace: namespace,
|
|
}
|
|
}
|
|
|
|
// Put stores a key-value pair in the distributed storage
|
|
func (c *Client) Put(ctx context.Context, key string, value []byte) error {
|
|
request := &StorageRequest{
|
|
Type: MessageTypePut,
|
|
Key: key,
|
|
Value: value,
|
|
Namespace: c.namespace,
|
|
}
|
|
|
|
return c.sendRequest(ctx, request)
|
|
}
|
|
|
|
// Get retrieves a value by key from the distributed storage
|
|
func (c *Client) Get(ctx context.Context, key string) ([]byte, error) {
|
|
request := &StorageRequest{
|
|
Type: MessageTypeGet,
|
|
Key: key,
|
|
Namespace: c.namespace,
|
|
}
|
|
|
|
response, err := c.sendRequestWithResponse(ctx, request)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !response.Success {
|
|
return nil, fmt.Errorf(response.Error)
|
|
}
|
|
|
|
return response.Value, nil
|
|
}
|
|
|
|
// Delete removes a key from the distributed storage
|
|
func (c *Client) Delete(ctx context.Context, key string) error {
|
|
request := &StorageRequest{
|
|
Type: MessageTypeDelete,
|
|
Key: key,
|
|
Namespace: c.namespace,
|
|
}
|
|
|
|
return c.sendRequest(ctx, request)
|
|
}
|
|
|
|
// List returns keys with a given prefix
|
|
func (c *Client) List(ctx context.Context, prefix string, limit int) ([]string, error) {
|
|
request := &StorageRequest{
|
|
Type: MessageTypeList,
|
|
Prefix: prefix,
|
|
Limit: limit,
|
|
Namespace: c.namespace,
|
|
}
|
|
|
|
response, err := c.sendRequestWithResponse(ctx, request)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !response.Success {
|
|
return nil, fmt.Errorf(response.Error)
|
|
}
|
|
|
|
return response.Keys, nil
|
|
}
|
|
|
|
// Exists checks if a key exists in the distributed storage
|
|
func (c *Client) Exists(ctx context.Context, key string) (bool, error) {
|
|
request := &StorageRequest{
|
|
Type: MessageTypeExists,
|
|
Key: key,
|
|
Namespace: c.namespace,
|
|
}
|
|
|
|
response, err := c.sendRequestWithResponse(ctx, request)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if !response.Success {
|
|
return false, fmt.Errorf(response.Error)
|
|
}
|
|
|
|
return response.Exists, nil
|
|
}
|
|
|
|
// sendRequest sends a request without expecting a response
|
|
func (c *Client) sendRequest(ctx context.Context, request *StorageRequest) error {
|
|
_, err := c.sendRequestWithResponse(ctx, request)
|
|
return err
|
|
}
|
|
|
|
// sendRequestWithResponse sends a request and waits for a response
|
|
func (c *Client) sendRequestWithResponse(ctx context.Context, request *StorageRequest) (*StorageResponse, error) {
|
|
// Get connected peers
|
|
peers := c.host.Network().Peers()
|
|
if len(peers) == 0 {
|
|
return nil, fmt.Errorf("no peers connected")
|
|
}
|
|
|
|
// Try to send to the first available peer
|
|
// In a production system, you might want to implement peer selection logic
|
|
for _, peerID := range peers {
|
|
response, err := c.sendToPeer(ctx, peerID, request)
|
|
if err != nil {
|
|
c.logger.Debug("Failed to send to peer",
|
|
zap.String("peer", peerID.String()),
|
|
zap.Error(err))
|
|
continue
|
|
}
|
|
return response, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("failed to send request to any peer")
|
|
}
|
|
|
|
// sendToPeer sends a request to a specific peer
|
|
func (c *Client) sendToPeer(ctx context.Context, peerID peer.ID, request *StorageRequest) (*StorageResponse, error) {
|
|
// Create a new stream to the peer
|
|
stream, err := c.host.NewStream(ctx, peerID, protocol.ID(StorageProtocolID))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create stream: %w", err)
|
|
}
|
|
defer stream.Close()
|
|
|
|
// Set deadline for the operation
|
|
deadline, ok := ctx.Deadline()
|
|
if ok {
|
|
stream.SetDeadline(deadline)
|
|
} else {
|
|
stream.SetDeadline(time.Now().Add(30 * time.Second))
|
|
}
|
|
|
|
// Marshal and send request
|
|
requestData, err := request.Marshal()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
if _, err := stream.Write(requestData); err != nil {
|
|
return nil, fmt.Errorf("failed to write request: %w", err)
|
|
}
|
|
|
|
// Close write side to signal end of request
|
|
if err := stream.CloseWrite(); err != nil {
|
|
return nil, fmt.Errorf("failed to close write: %w", err)
|
|
}
|
|
|
|
// Read response
|
|
responseData, err := io.ReadAll(stream)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
|
|
// Unmarshal response
|
|
var response StorageResponse
|
|
if err := response.Unmarshal(responseData); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
|
}
|
|
|
|
return &response, nil
|
|
}
|