Complete BZZZ functionality port to CHORUS
🎭 CHORUS now contains full BZZZ functionality adapted for containers Core systems ported: - P2P networking (libp2p with DHT and PubSub) - Task coordination (COOEE protocol) - HMMM collaborative reasoning - SHHH encryption and security - SLURP admin election system - UCXL content addressing - UCXI server integration - Hypercore logging system - Health monitoring and graceful shutdown - License validation with KACHING Container adaptations: - Environment variable configuration (no YAML files) - Container-optimized logging to stdout/stderr - Auto-generated agent IDs for container deployments - Docker-first architecture All proven BZZZ P2P protocols, AI integration, and collaboration features are now available in containerized form. Next: Build and test container deployment. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
289
pkg/ucxi/storage.go
Normal file
289
pkg/ucxi/storage.go
Normal file
@@ -0,0 +1,289 @@
|
||||
package ucxi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// BasicContentStorage provides a basic file-system based implementation of ContentStorage
|
||||
type BasicContentStorage struct {
|
||||
basePath string
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// NewBasicContentStorage creates a new basic content storage
|
||||
func NewBasicContentStorage(basePath string) (*BasicContentStorage, error) {
|
||||
// Ensure base directory exists
|
||||
if err := os.MkdirAll(basePath, 0755); err != nil {
|
||||
return nil, fmt.Errorf("failed to create storage directory: %w", err)
|
||||
}
|
||||
|
||||
return &BasicContentStorage{
|
||||
basePath: basePath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Store stores content with the given key
|
||||
func (s *BasicContentStorage) Store(ctx context.Context, key string, content *Content) error {
|
||||
if key == "" {
|
||||
return fmt.Errorf("key cannot be empty")
|
||||
}
|
||||
if content == nil {
|
||||
return fmt.Errorf("content cannot be nil")
|
||||
}
|
||||
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
// Generate file path
|
||||
filePath := s.getFilePath(key)
|
||||
|
||||
// Ensure directory exists
|
||||
dir := filepath.Dir(filePath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create directory %s: %w", dir, err)
|
||||
}
|
||||
|
||||
// Calculate checksum if not provided
|
||||
if content.Checksum == "" {
|
||||
hash := sha256.Sum256(content.Data)
|
||||
content.Checksum = hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// Serialize content to JSON
|
||||
data, err := json.MarshalIndent(content, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to serialize content: %w", err)
|
||||
}
|
||||
|
||||
// Write to file
|
||||
if err := ioutil.WriteFile(filePath, data, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write content file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retrieve retrieves content by key
|
||||
func (s *BasicContentStorage) Retrieve(ctx context.Context, key string) (*Content, error) {
|
||||
if key == "" {
|
||||
return nil, fmt.Errorf("key cannot be empty")
|
||||
}
|
||||
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
|
||||
filePath := s.getFilePath(key)
|
||||
|
||||
// Check if file exists
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("content not found for key: %s", key)
|
||||
}
|
||||
|
||||
// Read file
|
||||
data, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read content file: %w", err)
|
||||
}
|
||||
|
||||
// Deserialize content
|
||||
var content Content
|
||||
if err := json.Unmarshal(data, &content); err != nil {
|
||||
return nil, fmt.Errorf("failed to deserialize content: %w", err)
|
||||
}
|
||||
|
||||
// Verify checksum if available
|
||||
if content.Checksum != "" {
|
||||
hash := sha256.Sum256(content.Data)
|
||||
expectedChecksum := hex.EncodeToString(hash[:])
|
||||
if content.Checksum != expectedChecksum {
|
||||
return nil, fmt.Errorf("content checksum mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
return &content, nil
|
||||
}
|
||||
|
||||
// Delete deletes content by key
|
||||
func (s *BasicContentStorage) Delete(ctx context.Context, key string) error {
|
||||
if key == "" {
|
||||
return fmt.Errorf("key cannot be empty")
|
||||
}
|
||||
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
filePath := s.getFilePath(key)
|
||||
|
||||
// Check if file exists
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("content not found for key: %s", key)
|
||||
}
|
||||
|
||||
// Remove file
|
||||
if err := os.Remove(filePath); err != nil {
|
||||
return fmt.Errorf("failed to delete content file: %w", err)
|
||||
}
|
||||
|
||||
// Try to remove empty directories
|
||||
s.cleanupEmptyDirs(filepath.Dir(filePath))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// List lists all keys with the given prefix
|
||||
func (s *BasicContentStorage) List(ctx context.Context, prefix string) ([]string, error) {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
|
||||
var keys []string
|
||||
|
||||
// Walk through storage directory
|
||||
err := filepath.Walk(s.basePath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Skip directories
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Skip non-JSON files
|
||||
if !strings.HasSuffix(path, ".json") {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert file path back to key
|
||||
relPath, err := filepath.Rel(s.basePath, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove .json extension
|
||||
key := strings.TrimSuffix(relPath, ".json")
|
||||
|
||||
// Convert file path separators back to key format
|
||||
key = strings.ReplaceAll(key, string(filepath.Separator), "/")
|
||||
|
||||
// Check prefix match
|
||||
if prefix == "" || strings.HasPrefix(key, prefix) {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list storage contents: %w", err)
|
||||
}
|
||||
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// getFilePath converts a storage key to a file path
|
||||
func (s *BasicContentStorage) getFilePath(key string) string {
|
||||
// Sanitize key by replacing potentially problematic characters
|
||||
sanitized := strings.ReplaceAll(key, ":", "_")
|
||||
sanitized = strings.ReplaceAll(sanitized, "@", "_at_")
|
||||
sanitized = strings.ReplaceAll(sanitized, "/", string(filepath.Separator))
|
||||
|
||||
return filepath.Join(s.basePath, sanitized+".json")
|
||||
}
|
||||
|
||||
// cleanupEmptyDirs removes empty directories up the tree
|
||||
func (s *BasicContentStorage) cleanupEmptyDirs(dir string) {
|
||||
// Don't remove the base directory
|
||||
if dir == s.basePath {
|
||||
return
|
||||
}
|
||||
|
||||
// Try to remove directory if empty
|
||||
if err := os.Remove(dir); err == nil {
|
||||
// Successfully removed, try parent
|
||||
s.cleanupEmptyDirs(filepath.Dir(dir))
|
||||
}
|
||||
}
|
||||
|
||||
// GetStorageStats returns statistics about the storage
|
||||
func (s *BasicContentStorage) GetStorageStats() (map[string]interface{}, error) {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
|
||||
var fileCount int
|
||||
var totalSize int64
|
||||
|
||||
err := filepath.Walk(s.basePath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() && strings.HasSuffix(path, ".json") {
|
||||
fileCount++
|
||||
totalSize += info.Size()
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to calculate storage stats: %w", err)
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"file_count": fileCount,
|
||||
"total_size": totalSize,
|
||||
"base_path": s.basePath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Exists checks if content exists for the given key
|
||||
func (s *BasicContentStorage) Exists(ctx context.Context, key string) (bool, error) {
|
||||
if key == "" {
|
||||
return false, fmt.Errorf("key cannot be empty")
|
||||
}
|
||||
|
||||
filePath := s.getFilePath(key)
|
||||
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
|
||||
_, err := os.Stat(filePath)
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to check file existence: %w", err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Clear removes all content from storage
|
||||
func (s *BasicContentStorage) Clear(ctx context.Context) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
// Remove all contents of base directory
|
||||
entries, err := ioutil.ReadDir(s.basePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read storage directory: %w", err)
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
path := filepath.Join(s.basePath, entry.Name())
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return fmt.Errorf("failed to remove %s: %w", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user