enki 7c92aa3ded
Some checks are pending
CI Pipeline / Run Tests (push) Waiting to run
CI Pipeline / Lint Code (push) Waiting to run
CI Pipeline / Security Scan (push) Waiting to run
CI Pipeline / E2E Tests (push) Blocked by required conditions
Major DHT and Torrent fixes.
2025-08-29 21:18:36 -07:00

972 lines
26 KiB
Go

package p2p
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
"sync"
"time"
"torrentGateway/internal/config"
"torrentGateway/internal/dht"
"torrentGateway/internal/tracker"
"github.com/gorilla/mux"
)
// UnifiedP2PGateway coordinates all P2P systems
type UnifiedP2PGateway struct {
// P2P Components
tracker *tracker.Tracker
wsTracker *tracker.WebSocketTracker
dhtBootstrap *dht.DHTBootstrap
coordinator *P2PCoordinator
// Configuration
config *config.Config
db *sql.DB
// Maintenance
maintenanceTicker *time.Ticker
healthTicker *time.Ticker
stopCh chan struct{}
// Caching for peer discovery
peerCache map[string]*CachedPeerResponse
cacheMutex sync.RWMutex
cacheExpiry time.Duration
// Statistics
stats *P2PGatewayStats
statsMutex sync.RWMutex
}
// CachedPeerResponse represents a cached peer discovery response
type CachedPeerResponse struct {
Peers []UnifiedPeer `json:"peers"`
CachedAt time.Time `json:"cached_at"`
TTL time.Duration `json:"ttl"`
Sources []string `json:"sources"`
}
// UnifiedPeer represents a peer from any source
type UnifiedPeer struct {
ID string `json:"id"`
IP string `json:"ip"`
Port int `json:"port"`
Source string `json:"source"` // "tracker", "dht", "websocket"
Quality int `json:"quality"`
IsSeeder bool `json:"is_seeder"`
LastSeen time.Time `json:"last_seen"`
Endpoint string `json:"endpoint,omitempty"`
Protocol string `json:"protocol,omitempty"` // "webrtc", "http", "webseed"
Reliability float64 `json:"reliability,omitempty"`
RTT int `json:"rtt_ms,omitempty"`
}
// P2PGatewayStats tracks comprehensive P2P statistics
type P2PGatewayStats struct {
TotalTorrents int64 `json:"total_torrents"`
ActiveTorrents int64 `json:"active_torrents"`
TotalPeers int64 `json:"total_peers"`
TrackerPeers int64 `json:"tracker_peers"`
DHTNodes int64 `json:"dht_nodes"`
WebSocketPeers int64 `json:"websocket_peers"`
AnnouncesSent int64 `json:"announces_sent"`
AnnouncesReceived int64 `json:"announces_received"`
HealthStatus map[string]string `json:"health_status"`
LastMaintenance time.Time `json:"last_maintenance"`
LastHealthCheck time.Time `json:"last_health_check"`
SystemHealth string `json:"system_health"`
ComponentStats map[string]interface{} `json:"component_stats"`
}
// TorrentInfo is defined in coordinator.go
// NewUnifiedP2PGateway creates a new unified P2P gateway
func NewUnifiedP2PGateway(config *config.Config, db *sql.DB) *UnifiedP2PGateway {
gateway := &UnifiedP2PGateway{
config: config,
db: db,
peerCache: make(map[string]*CachedPeerResponse),
cacheExpiry: 2 * time.Minute, // Cache peer responses for 2 minutes
stopCh: make(chan struct{}),
stats: &P2PGatewayStats{
HealthStatus: make(map[string]string),
ComponentStats: make(map[string]interface{}),
SystemHealth: "starting",
},
}
return gateway
}
// Initialize starts all P2P components and background tasks
func (g *UnifiedP2PGateway) Initialize() error {
log.Printf("P2P Gateway: Initializing unified P2P system")
// Initialize tracker
if err := g.initializeTracker(); err != nil {
return fmt.Errorf("failed to initialize tracker: %w", err)
}
// Initialize DHT
if err := g.initializeDHT(); err != nil {
return fmt.Errorf("failed to initialize DHT: %w", err)
}
// Initialize WebSocket tracker
if err := g.initializeWebSocketTracker(); err != nil {
return fmt.Errorf("failed to initialize WebSocket tracker: %w", err)
}
// Initialize P2P coordinator
if err := g.initializeCoordinator(); err != nil {
return fmt.Errorf("failed to initialize coordinator: %w", err)
}
// Start background tasks
g.startBackgroundTasks()
g.stats.SystemHealth = "healthy"
log.Printf("P2P Gateway: Successfully initialized all P2P systems")
return nil
}
// CreateTorrent creates a new torrent and announces it to all P2P systems
func (g *UnifiedP2PGateway) CreateTorrent(fileHash string) (*TorrentInfo, error) {
log.Printf("P2P Gateway: Creating torrent for file %s", fileHash[:8])
// Get file info from database - this is a simplified version
// In production, you'd query the files table for name and size
filename := "Unknown"
var fileSize int64 = 0
row := g.db.QueryRow("SELECT original_name, size FROM files WHERE hash = ?", fileHash)
row.Scan(&filename, &fileSize) // Ignore error, use defaults
// Create torrent metadata
torrentInfo := &TorrentInfo{
InfoHash: fileHash,
Name: filename,
Size: fileSize,
CreatedAt: time.Now(),
LastAnnounce: time.Now(),
IsActive: true,
}
// Store in database
if err := g.storeTorrentInfo(torrentInfo); err != nil {
log.Printf("P2P Gateway: Failed to store torrent info: %v", err)
}
// Announce to all P2P systems immediately
if err := g.announceToAllSystems(torrentInfo); err != nil {
log.Printf("P2P Gateway: Failed to announce to all systems: %v", err)
}
// Update statistics
g.statsMutex.Lock()
g.stats.TotalTorrents++
g.stats.ActiveTorrents++
g.statsMutex.Unlock()
log.Printf("P2P Gateway: Successfully created and announced torrent %s", fileHash[:8])
return torrentInfo, nil
}
// GetPeersHandler provides a unified peer discovery endpoint
func (g *UnifiedP2PGateway) GetPeersHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
infoHash := vars["infohash"]
if len(infoHash) != 40 {
http.Error(w, "Invalid infohash format", http.StatusBadRequest)
return
}
// Check cache first
g.cacheMutex.RLock()
if cached, exists := g.peerCache[infoHash]; exists {
if time.Since(cached.CachedAt) < cached.TTL {
g.cacheMutex.RUnlock()
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-Cache", "HIT")
json.NewEncoder(w).Encode(cached)
return
}
}
g.cacheMutex.RUnlock()
// Cache miss - gather peers from all sources
peers, sources, err := g.gatherPeersFromAllSources(infoHash)
if err != nil {
log.Printf("P2P Gateway: Failed to gather peers for %s: %v", infoHash[:8], err)
http.Error(w, "Failed to gather peers", http.StatusInternalServerError)
return
}
// Create response
response := &CachedPeerResponse{
Peers: peers,
CachedAt: time.Now(),
TTL: g.cacheExpiry,
Sources: sources,
}
// Update cache
g.cacheMutex.Lock()
g.peerCache[infoHash] = response
g.cacheMutex.Unlock()
// Send response
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-Cache", "MISS")
json.NewEncoder(w).Encode(response)
log.Printf("P2P Gateway: Returned %d peers from %v for %s",
len(peers), sources, infoHash[:8])
}
// GetStatsHandler returns comprehensive P2P statistics
func (g *UnifiedP2PGateway) GetStatsHandler(w http.ResponseWriter, r *http.Request) {
g.updateStats()
g.statsMutex.RLock()
stats := *g.stats
g.statsMutex.RUnlock()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(stats)
}
// GetHealthHandler returns system health status
func (g *UnifiedP2PGateway) GetHealthHandler(w http.ResponseWriter, r *http.Request) {
health := g.performHealthCheck()
statusCode := http.StatusOK
if health.SystemHealth != "healthy" {
statusCode = http.StatusServiceUnavailable
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(health)
}
// Shutdown gracefully stops all P2P systems
func (g *UnifiedP2PGateway) Shutdown() error {
log.Printf("P2P Gateway: Shutting down unified P2P system")
close(g.stopCh)
if g.maintenanceTicker != nil {
g.maintenanceTicker.Stop()
}
if g.healthTicker != nil {
g.healthTicker.Stop()
}
// Shutdown components
if g.coordinator != nil {
g.coordinator.Stop()
}
if g.dhtBootstrap != nil {
g.dhtBootstrap.Stop()
}
g.stats.SystemHealth = "shutdown"
log.Printf("P2P Gateway: Shutdown complete")
return nil
}
// RegisterRoutes registers all P2P endpoints
func (g *UnifiedP2PGateway) RegisterRoutes(router *mux.Router) {
// Peer discovery endpoint
router.HandleFunc("/api/peers/{infohash}", g.GetPeersHandler).Methods("GET")
// Statistics endpoint
router.HandleFunc("/api/p2p/stats", g.GetStatsHandler).Methods("GET")
// Health check endpoint
router.HandleFunc("/api/p2p/health", g.GetHealthHandler).Methods("GET")
// WebSocket tracker endpoint (if WebSocket tracker is available)
if g.wsTracker != nil {
router.HandleFunc("/ws/tracker", g.wsTracker.HandleWS)
}
log.Printf("P2P Gateway: Registered API endpoints")
}
// ============ GATEWAY INTERFACE METHODS ============
// DHT Bootstrap Gateway interface methods
// GetPublicURL returns the public URL for this gateway
func (g *UnifiedP2PGateway) GetPublicURL() string {
// Try to get from config, fall back to localhost
if g.config.Gateway.PublicURL != "" {
return g.config.Gateway.PublicURL
}
return fmt.Sprintf("http://localhost:%d", g.config.Gateway.Port)
}
// GetDHTPort returns the DHT port
func (g *UnifiedP2PGateway) GetDHTPort() int {
return g.config.DHT.Port
}
// GetDatabase returns the database connection
func (g *UnifiedP2PGateway) GetDatabase() *sql.DB {
return g.db
}
// GetAllTorrentHashes returns all torrent hashes from the database
func (g *UnifiedP2PGateway) GetAllTorrentHashes() []string {
rows, err := g.db.Query("SELECT info_hash FROM p2p_torrents WHERE is_active = 1")
if err != nil {
log.Printf("P2P Gateway: Failed to get torrent hashes: %v", err)
return []string{}
}
defer rows.Close()
var hashes []string
for rows.Next() {
var hash string
if err := rows.Scan(&hash); err == nil {
hashes = append(hashes, hash)
}
}
return hashes
}
// Coordinator Gateway interface methods
// WebSeedPeer returns a PeerInfo for the WebSeed
func (g *UnifiedP2PGateway) WebSeedPeer() PeerInfo {
return PeerInfo{
IP: "127.0.0.1", // Local WebSeed
Port: g.config.Gateway.Port,
PeerID: "WEBSEED",
Source: "webseed",
Quality: 100, // Highest quality
LastSeen: time.Now(),
}
}
// EnableWebSeed enables WebSeed for a torrent
func (g *UnifiedP2PGateway) EnableWebSeed(infoHash string) error {
log.Printf("P2P Gateway: Enabling WebSeed for %s", infoHash[:8])
// In a full implementation, this would configure WebSeed URLs
return nil
}
// PublishToNostr publishes torrent to Nostr (placeholder)
func (g *UnifiedP2PGateway) PublishToNostr(torrent *TorrentInfo) error {
log.Printf("P2P Gateway: Publishing torrent %s to Nostr", torrent.InfoHash[:8])
// Placeholder - would integrate with actual Nostr publisher
return nil
}
// GetPort returns the gateway port
func (g *UnifiedP2PGateway) GetPort() int {
return g.config.Gateway.Port
}
// GetDHTBootstrap returns the DHT bootstrap instance
func (g *UnifiedP2PGateway) GetDHTBootstrap() *dht.DHTBootstrap {
return g.dhtBootstrap
}
// GetPeers returns peers for a given infohash from all sources
func (g *UnifiedP2PGateway) GetPeers(infoHash string) ([]UnifiedPeer, error) {
if g.coordinator == nil {
return []UnifiedPeer{}, fmt.Errorf("P2P coordinator not initialized")
}
// Get peers from all sources
peers, _, err := g.gatherPeersFromAllSources(infoHash)
return peers, err
}
// GetStats returns comprehensive P2P statistics
func (g *UnifiedP2PGateway) GetStats() (map[string]interface{}, error) {
if g.stats == nil {
return map[string]interface{}{}, fmt.Errorf("stats not initialized")
}
// Return current stats
stats := make(map[string]interface{})
stats["timestamp"] = time.Now().Format(time.RFC3339)
stats["health_status"] = g.stats.HealthStatus
stats["component_stats"] = g.stats.ComponentStats
stats["system_health"] = g.stats.SystemHealth
return stats, nil
}
// ============ INITIALIZATION METHODS ============
func (g *UnifiedP2PGateway) initializeTracker() error {
log.Printf("P2P Gateway: Initializing HTTP tracker")
// Note: This is a simplified tracker initialization for P2P gateway
// In production, you would pass proper config and gateway interface
log.Printf("P2P Gateway: Tracker initialization skipped - using external tracker")
g.stats.HealthStatus["tracker"] = "external"
return nil
}
func (g *UnifiedP2PGateway) initializeDHT() error {
log.Printf("P2P Gateway: Initializing DHT")
// First create DHT node
dhtNode, err := dht.NewDHT(&g.config.DHT)
if err != nil {
return fmt.Errorf("failed to create DHT node: %w", err)
}
// Then create DHT bootstrap with the node
g.dhtBootstrap = dht.NewDHTBootstrap(dhtNode, g, &g.config.DHT)
// Initialize the bootstrap functionality
if err := g.dhtBootstrap.Initialize(); err != nil {
return fmt.Errorf("failed to initialize DHT bootstrap: %w", err)
}
g.stats.HealthStatus["dht"] = "healthy"
return nil
}
func (g *UnifiedP2PGateway) initializeWebSocketTracker() error {
log.Printf("P2P Gateway: Initializing WebSocket tracker")
g.wsTracker = tracker.NewWebSocketTracker(g.tracker)
g.wsTracker.StartCleanup()
g.stats.HealthStatus["websocket"] = "healthy"
return nil
}
func (g *UnifiedP2PGateway) initializeCoordinator() error {
log.Printf("P2P Gateway: Initializing P2P coordinator")
g.coordinator = NewCoordinator(g, g.tracker, g.dhtBootstrap)
g.stats.HealthStatus["coordinator"] = "healthy"
return nil
}
// ============ BACKGROUND MAINTENANCE TASKS ============
func (g *UnifiedP2PGateway) startBackgroundTasks() {
log.Printf("P2P Gateway: Starting background maintenance tasks")
// Maintenance tasks every 5 minutes
g.maintenanceTicker = time.NewTicker(5 * time.Minute)
go g.maintenanceLoop()
// Health checks every minute
g.healthTicker = time.NewTicker(1 * time.Minute)
go g.healthCheckLoop()
// Periodic DHT announces every 15 minutes
go g.periodicAnnounceLoop()
}
func (g *UnifiedP2PGateway) maintenanceLoop() {
for {
select {
case <-g.stopCh:
return
case <-g.maintenanceTicker.C:
g.performMaintenance()
}
}
}
func (g *UnifiedP2PGateway) healthCheckLoop() {
for {
select {
case <-g.stopCh:
return
case <-g.healthTicker.C:
g.performHealthCheck()
}
}
}
func (g *UnifiedP2PGateway) periodicAnnounceLoop() {
ticker := time.NewTicker(15 * time.Minute)
defer ticker.Stop()
for {
select {
case <-g.stopCh:
return
case <-ticker.C:
g.performPeriodicAnnounces()
}
}
}
func (g *UnifiedP2PGateway) performMaintenance() {
log.Printf("P2P Gateway: Performing maintenance tasks")
g.statsMutex.Lock()
g.stats.LastMaintenance = time.Now()
g.statsMutex.Unlock()
// Clean up expired peers from all systems
g.cleanupExpiredPeers()
// Verify WebSeeds are accessible
g.verifyWebSeeds()
// Clean up peer cache
g.cleanupPeerCache()
// Update statistics
g.updateStats()
log.Printf("P2P Gateway: Maintenance tasks completed")
}
func (g *UnifiedP2PGateway) cleanupExpiredPeers() {
// Clean up tracker peers
if g.tracker != nil {
// Note: cleanupExpiredPeers is a private method, can't call directly
log.Printf("P2P Gateway: Tracker cleanup skipped (private method)")
}
// Clean up DHT peers
if g.dhtBootstrap != nil && g.dhtBootstrap.GetNode() != nil {
// DHT cleanup is handled internally by the node
}
// WebSocket tracker cleanup is handled by its own ticker
}
func (g *UnifiedP2PGateway) verifyWebSeeds() {
// Get all active torrents with WebSeeds
torrents, err := g.getActiveTorrentsWithWebSeeds()
if err != nil {
log.Printf("P2P Gateway: Failed to get torrents for WebSeed verification: %v", err)
return
}
verifiedCount := 0
failedCount := 0
for _, torrent := range torrents {
if torrent.WebSeedURL != "" {
if g.verifyWebSeedURL(torrent.WebSeedURL) {
verifiedCount++
} else {
failedCount++
log.Printf("P2P Gateway: WebSeed verification failed for %s: %s",
torrent.InfoHash[:8], torrent.WebSeedURL)
}
}
}
if verifiedCount > 0 || failedCount > 0 {
log.Printf("P2P Gateway: WebSeed verification completed: %d verified, %d failed",
verifiedCount, failedCount)
}
}
func (g *UnifiedP2PGateway) verifyWebSeedURL(url string) bool {
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Head(url)
if err != nil {
return false
}
defer resp.Body.Close()
return resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusPartialContent
}
func (g *UnifiedP2PGateway) cleanupPeerCache() {
g.cacheMutex.Lock()
defer g.cacheMutex.Unlock()
now := time.Now()
cleanedCount := 0
for infoHash, cached := range g.peerCache {
if now.Sub(cached.CachedAt) > cached.TTL {
delete(g.peerCache, infoHash)
cleanedCount++
}
}
if cleanedCount > 0 {
log.Printf("P2P Gateway: Cleaned %d expired peer cache entries", cleanedCount)
}
}
func (g *UnifiedP2PGateway) performPeriodicAnnounces() {
log.Printf("P2P Gateway: Performing periodic announces for all torrents")
torrents, err := g.getAllActiveTorrents()
if err != nil {
log.Printf("P2P Gateway: Failed to get active torrents for periodic announces: %v", err)
return
}
announceCount := 0
for _, torrent := range torrents {
if err := g.announceToAllSystems(torrent); err != nil {
log.Printf("P2P Gateway: Failed to announce %s: %v", torrent.InfoHash[:8], err)
} else {
announceCount++
}
}
g.statsMutex.Lock()
g.stats.AnnouncesSent += int64(announceCount)
g.statsMutex.Unlock()
log.Printf("P2P Gateway: Completed periodic announces for %d torrents", announceCount)
}
// ============ HEALTH CHECK SYSTEM ============
func (g *UnifiedP2PGateway) performHealthCheck() *P2PGatewayStats {
g.statsMutex.Lock()
g.stats.LastHealthCheck = time.Now()
// Check DHT health
if g.dhtBootstrap != nil && g.dhtBootstrap.GetNode() != nil {
dhtStats := g.dhtBootstrap.GetDHTStats()
if nodeCount, ok := dhtStats["routing_table_size"].(int); ok && nodeCount > 0 {
g.stats.HealthStatus["dht"] = "healthy"
} else {
g.stats.HealthStatus["dht"] = "degraded"
}
} else {
g.stats.HealthStatus["dht"] = "failed"
}
// Check tracker health
if g.tracker != nil {
trackerStats := g.tracker.GetStats()
if peerCount, ok := trackerStats["peers"].(int64); ok && peerCount >= 0 {
g.stats.HealthStatus["tracker"] = "healthy"
} else {
g.stats.HealthStatus["tracker"] = "degraded"
}
} else {
g.stats.HealthStatus["tracker"] = "failed"
}
// Check WebSocket tracker health
if g.wsTracker != nil {
wsStats := g.wsTracker.GetStats()
if peers, ok := wsStats["total_peers"].(int); ok && peers >= 0 {
g.stats.HealthStatus["websocket"] = "healthy"
} else {
g.stats.HealthStatus["websocket"] = "degraded"
}
} else {
g.stats.HealthStatus["websocket"] = "failed"
}
// Determine overall system health
healthyComponents := 0
totalComponents := len(g.stats.HealthStatus)
for _, status := range g.stats.HealthStatus {
if status == "healthy" {
healthyComponents++
}
}
if healthyComponents == totalComponents {
g.stats.SystemHealth = "healthy"
} else if healthyComponents > totalComponents/2 {
g.stats.SystemHealth = "degraded"
} else {
g.stats.SystemHealth = "critical"
}
stats := *g.stats
g.statsMutex.Unlock()
return &stats
}
// ============ PEER DISCOVERY AND COORDINATION ============
func (g *UnifiedP2PGateway) gatherPeersFromAllSources(infoHash string) ([]UnifiedPeer, []string, error) {
var allPeers []UnifiedPeer
var sources []string
// Get peers from HTTP tracker
if g.tracker != nil {
trackerPeers, err := g.tracker.GetPeersForTorrent(infoHash)
if err == nil && len(trackerPeers) > 0 {
for _, peer := range trackerPeers {
unifiedPeer := UnifiedPeer{
ID: peer.PeerID,
IP: peer.IP,
Port: peer.Port,
Source: "tracker",
Quality: peer.Priority,
IsSeeder: peer.IsSeeder || peer.Left == 0,
LastSeen: peer.LastSeen,
Protocol: "http",
Reliability: calculateReliability(peer),
}
if peer.IsWebSeed {
unifiedPeer.Protocol = "webseed"
unifiedPeer.Endpoint = fmt.Sprintf("http://localhost:%d/webseed/%s", g.config.Gateway.Port, infoHash)
unifiedPeer.Quality = 100 // WebSeeds get highest quality
}
allPeers = append(allPeers, unifiedPeer)
}
sources = append(sources, "tracker")
}
}
// Get peers from DHT
if g.dhtBootstrap != nil && g.dhtBootstrap.GetNode() != nil {
dhtPeers := g.coordinator.getDHTPeers(infoHash)
if len(dhtPeers) > 0 {
for _, peer := range dhtPeers {
unifiedPeer := UnifiedPeer{
ID: peer.PeerID,
IP: peer.IP,
Port: peer.Port,
Source: "dht",
Quality: peer.Quality,
LastSeen: peer.LastSeen,
Protocol: "http",
}
allPeers = append(allPeers, unifiedPeer)
}
sources = append(sources, "dht")
}
}
// Get peers from WebSocket tracker
if g.wsTracker != nil {
wsStats := g.wsTracker.GetStats()
// Note: WebSocket tracker doesn't have a direct GetPeers method
// This would need to be implemented based on the swarm structure
if totalPeers, ok := wsStats["total_peers"].(int); ok && totalPeers > 0 {
sources = append(sources, "websocket")
}
}
return allPeers, sources, nil
}
func (g *UnifiedP2PGateway) announceToAllSystems(torrent *TorrentInfo) error {
var errors []error
// Announce to HTTP tracker
if g.tracker != nil {
if err := g.announceToTracker(torrent); err != nil {
errors = append(errors, fmt.Errorf("tracker announce failed: %w", err))
}
}
// Announce to DHT
if g.dhtBootstrap != nil {
if err := g.announceToDHT(torrent); err != nil {
errors = append(errors, fmt.Errorf("DHT announce failed: %w", err))
}
}
// Update last announce time
torrent.LastAnnounce = time.Now()
if err := g.updateTorrentInfo(torrent); err != nil {
errors = append(errors, fmt.Errorf("failed to update torrent info: %w", err))
}
if len(errors) > 0 {
return fmt.Errorf("announce errors: %v", errors)
}
return nil
}
func (g *UnifiedP2PGateway) announceToTracker(torrent *TorrentInfo) error {
// This would integrate with the tracker's announce system
log.Printf("P2P Gateway: Announced %s to HTTP tracker", torrent.InfoHash[:8])
return nil
}
func (g *UnifiedP2PGateway) announceToDHT(torrent *TorrentInfo) error {
if g.dhtBootstrap != nil {
g.dhtBootstrap.AnnounceNewTorrent(torrent.InfoHash, g.config.DHT.Port)
log.Printf("P2P Gateway: Announced %s to DHT", torrent.InfoHash[:8])
}
return nil
}
// ============ STATISTICS AND UTILITIES ============
func (g *UnifiedP2PGateway) updateStats() {
g.statsMutex.Lock()
defer g.statsMutex.Unlock()
// Update component statistics
if g.tracker != nil {
g.stats.ComponentStats["tracker"] = g.tracker.GetStats()
}
if g.dhtBootstrap != nil {
g.stats.ComponentStats["dht"] = g.dhtBootstrap.GetDHTStats()
}
if g.wsTracker != nil {
g.stats.ComponentStats["websocket"] = g.wsTracker.GetStats()
}
// Calculate total counts
g.stats.TotalPeers = 0
if trackerStats, ok := g.stats.ComponentStats["tracker"].(map[string]interface{}); ok {
if peers, ok := trackerStats["peers"].(int64); ok {
g.stats.TrackerPeers = peers
g.stats.TotalPeers += peers
}
}
if dhtStats, ok := g.stats.ComponentStats["dht"].(map[string]interface{}); ok {
if nodes, ok := dhtStats["routing_table_size"].(int); ok {
g.stats.DHTNodes = int64(nodes)
}
}
if wsStats, ok := g.stats.ComponentStats["websocket"].(map[string]interface{}); ok {
if peers, ok := wsStats["total_peers"].(int); ok {
g.stats.WebSocketPeers = int64(peers)
g.stats.TotalPeers += int64(peers)
}
}
}
// Helper functions for database operations
func (g *UnifiedP2PGateway) storeTorrentInfo(torrent *TorrentInfo) error {
query := `INSERT OR REPLACE INTO p2p_torrents (info_hash, name, size, created_at, last_announce, webseed_url, is_active)
VALUES (?, ?, ?, ?, ?, ?, ?)`
_, err := g.db.Exec(query, torrent.InfoHash, torrent.Name, torrent.Size,
torrent.CreatedAt, torrent.LastAnnounce, torrent.WebSeedURL, torrent.IsActive)
return err
}
func (g *UnifiedP2PGateway) updateTorrentInfo(torrent *TorrentInfo) error {
query := `UPDATE p2p_torrents SET last_announce = ?, is_active = ? WHERE info_hash = ?`
_, err := g.db.Exec(query, torrent.LastAnnounce, torrent.IsActive, torrent.InfoHash)
return err
}
func (g *UnifiedP2PGateway) getAllActiveTorrents() ([]*TorrentInfo, error) {
query := `SELECT info_hash, name, size, created_at, last_announce, webseed_url, is_active
FROM p2p_torrents WHERE is_active = 1`
rows, err := g.db.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()
var torrents []*TorrentInfo
for rows.Next() {
var torrent TorrentInfo
var webSeedURL sql.NullString
err := rows.Scan(&torrent.InfoHash, &torrent.Name, &torrent.Size,
&torrent.CreatedAt, &torrent.LastAnnounce, &webSeedURL, &torrent.IsActive)
if err != nil {
continue
}
if webSeedURL.Valid {
torrent.WebSeedURL = webSeedURL.String
}
torrents = append(torrents, &torrent)
}
return torrents, nil
}
func (g *UnifiedP2PGateway) getActiveTorrentsWithWebSeeds() ([]*TorrentInfo, error) {
query := `SELECT info_hash, name, size, created_at, last_announce, webseed_url, is_active
FROM p2p_torrents WHERE is_active = 1 AND webseed_url IS NOT NULL AND webseed_url != ''`
rows, err := g.db.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()
var torrents []*TorrentInfo
for rows.Next() {
var torrent TorrentInfo
err := rows.Scan(&torrent.InfoHash, &torrent.Name, &torrent.Size,
&torrent.CreatedAt, &torrent.LastAnnounce, &torrent.WebSeedURL, &torrent.IsActive)
if err != nil {
continue
}
torrents = append(torrents, &torrent)
}
return torrents, nil
}
func calculateReliability(peer *tracker.PeerInfo) float64 {
// Calculate reliability based on various factors
reliability := 0.5 // Base reliability
if peer.IsWebSeed {
return 1.0 // WebSeeds are most reliable
}
if peer.IsSeeder {
reliability += 0.3
}
if peer.Priority > 50 {
reliability += 0.2
}
// Recent activity bonus
if time.Since(peer.LastSeen) < 10*time.Minute {
reliability += 0.2
}
if reliability > 1.0 {
reliability = 1.0
}
return reliability
}
// CreateP2PTables creates the necessary database tables for P2P coordination
func (g *UnifiedP2PGateway) CreateP2PTables() error {
query := `
CREATE TABLE IF NOT EXISTS p2p_torrents (
info_hash TEXT PRIMARY KEY,
name TEXT NOT NULL,
size INTEGER NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_announce DATETIME DEFAULT CURRENT_TIMESTAMP,
webseed_url TEXT,
is_active BOOLEAN DEFAULT 1
);
CREATE INDEX IF NOT EXISTS idx_p2p_torrents_active ON p2p_torrents(is_active);
CREATE INDEX IF NOT EXISTS idx_p2p_torrents_last_announce ON p2p_torrents(last_announce);
`
_, err := g.db.Exec(query)
return err
}