more fuckingfixes
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

This commit is contained in:
Enki 2025-08-28 14:15:37 -07:00
parent c294ce4cff
commit 3b9bf95247
8 changed files with 225 additions and 54 deletions

View File

@ -89,12 +89,15 @@ func main() {
// Register API routes with /api prefix // Register API routes with /api prefix
apiRouter := r.PathPrefix("/api").Subrouter() apiRouter := r.PathPrefix("/api").Subrouter()
// Register tracker routes on main router (no /api prefix for BitTorrent compatibility) // Register main API routes and get gateway instance first
api.RegisterTrackerRoutes(r, cfg, storageBackend)
// Register main API routes and get gateway instance
gatewayInstance = api.RegisterRoutes(apiRouter, cfg, storageBackend) gatewayInstance = api.RegisterRoutes(apiRouter, cfg, storageBackend)
// Register tracker routes on main router (no /api prefix for BitTorrent compatibility)
wsTracker := api.RegisterTrackerRoutes(r, cfg, storageBackend, gatewayInstance)
if wsTracker != nil {
gatewayInstance.SetWebSocketTracker(wsTracker)
}
// Serve static files // Serve static files
webFS := web.GetFS() webFS := web.GetFS()
staticFS, _ := fs.Sub(webFS, "static") staticFS, _ := fs.Sub(webFS, "static")

View File

@ -15,6 +15,21 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
// TrackerInterface defines methods needed from the tracker
type TrackerInterface interface {
GetStats() map[string]interface{}
}
// WebSocketTrackerInterface defines methods needed from the WebSocket tracker
type WebSocketTrackerInterface interface {
GetStats() map[string]interface{}
}
// DHTInterface defines methods needed from the DHT
type DHTInterface interface {
GetStats() map[string]interface{}
}
// GatewayInterface defines the methods needed from the gateway // GatewayInterface defines the methods needed from the gateway
type GatewayInterface interface { type GatewayInterface interface {
GetDB() *sql.DB GetDB() *sql.DB
@ -23,6 +38,9 @@ type GatewayInterface interface {
CleanupOrphanedChunks() (map[string]interface{}, error) CleanupOrphanedChunks() (map[string]interface{}, error)
CleanupInactiveUsers(days int) (map[string]interface{}, error) CleanupInactiveUsers(days int) (map[string]interface{}, error)
ReconstructTorrentFile(fileHash, fileName string) (string, error) ReconstructTorrentFile(fileHash, fileName string) (string, error)
GetTrackerInstance() TrackerInterface
GetWebSocketTracker() WebSocketTrackerInterface
GetDHTNode() DHTInterface
} }
// TranscodingManager interface for transcoding operations // TranscodingManager interface for transcoding operations
@ -1243,11 +1261,35 @@ func (ah *AdminHandlers) gatherStreamingAnalytics(db *sql.DB) map[string]interfa
func (ah *AdminHandlers) gatherP2PHealthMetrics() map[string]interface{} { func (ah *AdminHandlers) gatherP2PHealthMetrics() map[string]interface{} {
p2p := make(map[string]interface{}) p2p := make(map[string]interface{})
// P2P health score calculation (mock implementation) // Get real P2P stats from tracker instance
p2p["health_score"] = 87 if tracker := ah.gateway.GetTrackerInstance(); tracker != nil {
p2p["dht_node_count"] = 1249 trackerStats := tracker.GetStats()
p2p["webseed_hit_ratio"] = "91.2%" if peers, ok := trackerStats["peers"]; ok {
p2p["torrent_completion_avg"] = "2.1 mins" p2p["active_peers"] = peers
}
}
// Get real DHT stats
if dht := ah.gateway.GetDHTNode(); dht != nil {
dhtStats := dht.GetStats()
if nodeCount, ok := dhtStats["routing_table_size"]; ok {
p2p["dht_node_count"] = nodeCount
}
if torrents, ok := dhtStats["torrents"]; ok {
p2p["dht_torrents"] = torrents
}
}
// Get WebSocket tracker stats
if wsTracker := ah.gateway.GetWebSocketTracker(); wsTracker != nil {
wsStats := wsTracker.GetStats()
if swarms, ok := wsStats["swarms"]; ok {
p2p["websocket_swarms"] = swarms
}
if peers, ok := wsStats["peers"]; ok {
p2p["websocket_peers"] = peers
}
}
return p2p return p2p
} }
@ -1256,12 +1298,14 @@ func (ah *AdminHandlers) gatherP2PHealthMetrics() map[string]interface{} {
func (ah *AdminHandlers) gatherWebTorrentStats() map[string]interface{} { func (ah *AdminHandlers) gatherWebTorrentStats() map[string]interface{} {
webtorrent := make(map[string]interface{}) webtorrent := make(map[string]interface{})
// WebTorrent peer statistics (mock data) // Get real WebSocket tracker stats (WebTorrent uses WebSocket tracker)
webtorrent["peers_active"] = 45 if wsTracker := ah.gateway.GetWebSocketTracker(); wsTracker != nil {
webtorrent["browser_client_types"] = map[string]int{ wsStats := wsTracker.GetStats()
"Chrome": 60, webtorrent["active_swarms"] = wsStats["swarms"]
"Firefox": 25, webtorrent["connected_peers"] = wsStats["peers"]
"Safari": 15, } else {
webtorrent["active_swarms"] = 0
webtorrent["connected_peers"] = 0
} }
return webtorrent return webtorrent

View File

@ -52,12 +52,13 @@ type UserStatsResponse struct {
// UserFile represents a file in user's file list // UserFile represents a file in user's file list
type UserFile struct { type UserFile struct {
Hash string `json:"hash"` Hash string `json:"hash"`
Name string `json:"name"` Name string `json:"name"`
Size int64 `json:"size"` Size int64 `json:"size"`
StorageType string `json:"storage_type"` StorageType string `json:"storage_type"`
AccessLevel string `json:"access_level"` AccessLevel string `json:"access_level"`
UploadedAt string `json:"uploaded_at"` UploadedAt string `json:"uploaded_at"`
TorrentInfoHash string `json:"torrent_info_hash,omitempty"`
} }
// LoginHandler handles user authentication // LoginHandler handles user authentication
@ -253,12 +254,13 @@ func (ah *AuthHandlers) UserFilesHandler(w http.ResponseWriter, r *http.Request)
if files != nil { if files != nil {
for _, file := range files { for _, file := range files {
userFiles = append(userFiles, UserFile{ userFiles = append(userFiles, UserFile{
Hash: file.Hash, Hash: file.Hash,
Name: file.OriginalName, Name: file.OriginalName,
Size: file.Size, Size: file.Size,
StorageType: file.StorageType, StorageType: file.StorageType,
AccessLevel: file.AccessLevel, AccessLevel: file.AccessLevel,
UploadedAt: file.CreatedAt.Format(time.RFC3339), UploadedAt: file.CreatedAt.Format(time.RFC3339),
TorrentInfoHash: file.InfoHash,
}) })
} }
} }

View File

@ -112,6 +112,8 @@ type Gateway struct {
publicURL string publicURL string
trackerInstance *tracker.Tracker trackerInstance *tracker.Tracker
dhtBootstrap DHTBootstrap dhtBootstrap DHTBootstrap
dhtNode *dht.DHTBootstrap // Add actual DHT instance
wsTracker *tracker.WebSocketTracker // Add WebSocket tracker instance
transcodingManager TranscodingManager transcodingManager TranscodingManager
} }
@ -330,11 +332,14 @@ type ChunkInfo struct {
} }
type UploadResponse struct { type UploadResponse struct {
FileHash string `json:"file_hash"` FileHash string `json:"file_hash"`
Message string `json:"message"` Message string `json:"message"`
TorrentHash string `json:"torrent_hash,omitempty"` TorrentHash string `json:"torrent_hash,omitempty"`
MagnetLink string `json:"magnet_link,omitempty"` MagnetLink string `json:"magnet_link,omitempty"`
NostrEventID string `json:"nostr_event_id,omitempty"` NostrEventID string `json:"nostr_event_id,omitempty"`
NIP71EventID string `json:"nip71_event_id,omitempty"`
StreamingURL string `json:"streaming_url,omitempty"`
HLSURL string `json:"hls_url,omitempty"`
} }
func NewGateway(cfg *config.Config, storage *storage.Backend) *Gateway { func NewGateway(cfg *config.Config, storage *storage.Backend) *Gateway {
@ -641,6 +646,39 @@ func (g *Gateway) GetAllTorrentHashes() []string {
// SetDHTBootstrap sets the DHT bootstrap instance for torrent announcements // SetDHTBootstrap sets the DHT bootstrap instance for torrent announcements
func (g *Gateway) SetDHTBootstrap(dhtBootstrap DHTBootstrap) { func (g *Gateway) SetDHTBootstrap(dhtBootstrap DHTBootstrap) {
g.dhtBootstrap = dhtBootstrap g.dhtBootstrap = dhtBootstrap
// Also store the actual DHT node instance
if dhtBootstrapConcrete, ok := dhtBootstrap.(*dht.DHTBootstrap); ok {
g.dhtNode = dhtBootstrapConcrete
}
}
// SetWebSocketTracker sets the WebSocket tracker instance for stats collection
func (g *Gateway) SetWebSocketTracker(wsTracker *tracker.WebSocketTracker) {
g.wsTracker = wsTracker
}
// GetTrackerInstance returns the tracker instance for admin interface
func (g *Gateway) GetTrackerInstance() admin.TrackerInterface {
if g.trackerInstance == nil {
return nil
}
return g.trackerInstance
}
// GetWebSocketTracker returns the WebSocket tracker instance for admin interface
func (g *Gateway) GetWebSocketTracker() admin.WebSocketTrackerInterface {
if g.wsTracker == nil {
return nil
}
return g.wsTracker
}
// GetDHTNode returns the DHT node instance for admin interface
func (g *Gateway) GetDHTNode() admin.DHTInterface {
if g.dhtNode == nil {
return nil
}
return g.dhtNode
} }
func (g *Gateway) UploadHandler(w http.ResponseWriter, r *http.Request) { func (g *Gateway) UploadHandler(w http.ResponseWriter, r *http.Request) {
@ -818,6 +856,7 @@ func (g *Gateway) handleBlobUpload(w http.ResponseWriter, r *http.Request, file
// Publish to Nostr for blobs // Publish to Nostr for blobs
var nostrEventID string var nostrEventID string
var nip71EventID string
if g.nostrPublisher != nil { if g.nostrPublisher != nil {
eventData := nostr.TorrentEventData{ eventData := nostr.TorrentEventData{
Title: fmt.Sprintf("File: %s", fileName), Title: fmt.Sprintf("File: %s", fileName),
@ -859,6 +898,7 @@ func (g *Gateway) handleBlobUpload(w http.ResponseWriter, r *http.Request, file
fmt.Printf("Warning: Failed to publish NIP-71 video event: %v\n", err) fmt.Printf("Warning: Failed to publish NIP-71 video event: %v\n", err)
} else { } else {
fmt.Printf("Published NIP-71 video event: %s\n", nip71Event.ID) fmt.Printf("Published NIP-71 video event: %s\n", nip71Event.ID)
nip71EventID = nip71Event.ID
} }
} }
} }
@ -875,6 +915,13 @@ func (g *Gateway) handleBlobUpload(w http.ResponseWriter, r *http.Request, file
FileHash: metadata.Hash, FileHash: metadata.Hash,
Message: "File uploaded successfully as blob", Message: "File uploaded successfully as blob",
NostrEventID: nostrEventID, NostrEventID: nostrEventID,
NIP71EventID: nip71EventID,
}
// Add streaming URL if it's a video
if streamingInfo != nil {
response.StreamingURL = fmt.Sprintf("%s/api/stream/%s", g.getBaseURL(), metadata.Hash)
response.HLSURL = fmt.Sprintf("%s/api/stream/%s/playlist.m3u8", g.getBaseURL(), metadata.Hash)
} }
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
@ -1052,6 +1099,7 @@ func (g *Gateway) handleTorrentUpload(w http.ResponseWriter, r *http.Request, fi
// Publish to Nostr // Publish to Nostr
var nostrEventID string var nostrEventID string
var nip71EventID string
if g.nostrPublisher != nil { if g.nostrPublisher != nil {
eventData := nostr.TorrentEventData{ eventData := nostr.TorrentEventData{
Title: fmt.Sprintf("Torrent: %s", fileName), Title: fmt.Sprintf("Torrent: %s", fileName),
@ -1096,6 +1144,7 @@ func (g *Gateway) handleTorrentUpload(w http.ResponseWriter, r *http.Request, fi
fmt.Printf("Warning: Failed to publish NIP-71 video event: %v\n", err) fmt.Printf("Warning: Failed to publish NIP-71 video event: %v\n", err)
} else { } else {
fmt.Printf("Published NIP-71 video event: %s\n", nip71Event.ID) fmt.Printf("Published NIP-71 video event: %s\n", nip71Event.ID)
nip71EventID = nip71Event.ID
} }
} }
} }
@ -1124,6 +1173,13 @@ func (g *Gateway) handleTorrentUpload(w http.ResponseWriter, r *http.Request, fi
TorrentHash: torrentInfo.InfoHash, TorrentHash: torrentInfo.InfoHash,
MagnetLink: torrentInfo.Magnet, MagnetLink: torrentInfo.Magnet,
NostrEventID: nostrEventID, NostrEventID: nostrEventID,
NIP71EventID: nip71EventID,
}
// Add streaming URL if it's a video
if streamingInfo != nil {
response.StreamingURL = fmt.Sprintf("%s/api/stream/%s", g.getBaseURL(), metadata.Hash)
response.HLSURL = fmt.Sprintf("%s/api/stream/%s/playlist.m3u8", g.getBaseURL(), metadata.Hash)
} }
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
@ -2247,24 +2303,37 @@ func (g *Gateway) P2PStatsHandler(w http.ResponseWriter, r *http.Request) {
stats := make(map[string]interface{}) stats := make(map[string]interface{})
// Tracker statistics // BitTorrent tracker statistics - get real stats from tracker instance
if g.trackerInstance != nil { if g.trackerInstance != nil {
trackerStats := make(map[string]interface{}) stats["tracker"] = g.trackerInstance.GetStats()
trackerStats["status"] = "active"
trackerStats["uptime_seconds"] = time.Since(time.Now()).Seconds() // Placeholder
stats["tracker"] = trackerStats
} }
// DHT statistics // WebSocket tracker statistics - get real stats from WebSocket tracker
if g.dhtBootstrap != nil { if g.wsTracker != nil {
dhtStats := make(map[string]interface{}) stats["websocket_tracker"] = g.wsTracker.GetStats()
dhtStats["status"] = "active" }
dhtStats["routing_table_size"] = "N/A" // Would need DHT interface methods
dhtStats["active_searches"] = 0 // DHT statistics - get real stats from DHT node
dhtStats["stored_values"] = 0 if g.dhtNode != nil {
// Get stats from the actual DHT node within the bootstrap
stats["dht"] = dhtStats if dhtNode := g.dhtNode.GetNode(); dhtNode != nil {
dhtStats := dhtNode.GetStats()
stats["dht"] = map[string]interface{}{
"status": "active",
"packets_sent": dhtStats.PacketsSent,
"packets_received": dhtStats.PacketsReceived,
"nodes_in_table": dhtStats.NodesInTable,
"stored_items": dhtStats.StoredItems,
}
}
} else if g.dhtBootstrap != nil {
// Fallback to placeholder if we don't have the node reference yet
stats["dht"] = map[string]interface{}{
"status": "active",
"routing_table_size": "N/A",
"active_searches": 0,
"stored_values": 0,
}
} }
// WebSeed statistics (from our enhanced implementation) // WebSeed statistics (from our enhanced implementation)
@ -2302,7 +2371,8 @@ func (g *Gateway) P2PStatsHandler(w http.ResponseWriter, r *http.Request) {
stats["coordination"] = map[string]interface{}{ stats["coordination"] = map[string]interface{}{
"integration_active": g.trackerInstance != nil && g.dhtBootstrap != nil, "integration_active": g.trackerInstance != nil && g.dhtBootstrap != nil,
"webseed_enabled": true, "webseed_enabled": true,
"total_components": 3, // Tracker + DHT + WebSeed "websocket_enabled": g.wsTracker != nil,
"total_components": 4, // Tracker + WebSocket Tracker + DHT + WebSeed
"timestamp": time.Now().Format(time.RFC3339), "timestamp": time.Now().Format(time.RFC3339),
} }
@ -4141,12 +4211,11 @@ func systemStatsHandler(gateway *Gateway, storage *storage.Backend, trackerInsta
} }
// RegisterTrackerRoutes registers tracker endpoints on the main router // RegisterTrackerRoutes registers tracker endpoints on the main router
func RegisterTrackerRoutes(r *mux.Router, cfg *config.Config, storage *storage.Backend) { func RegisterTrackerRoutes(r *mux.Router, cfg *config.Config, storage *storage.Backend, gateway *Gateway) *tracker.WebSocketTracker {
if !cfg.IsServiceEnabled("tracker") { if !cfg.IsServiceEnabled("tracker") {
return return nil
} }
gateway := NewGateway(cfg, storage)
trackerInstance := tracker.NewTracker(&cfg.Tracker, gateway) trackerInstance := tracker.NewTracker(&cfg.Tracker, gateway)
announceHandler := tracker.NewAnnounceHandler(trackerInstance) announceHandler := tracker.NewAnnounceHandler(trackerInstance)
scrapeHandler := tracker.NewScrapeHandler(trackerInstance) scrapeHandler := tracker.NewScrapeHandler(trackerInstance)
@ -4161,6 +4230,7 @@ func RegisterTrackerRoutes(r *mux.Router, cfg *config.Config, storage *storage.B
r.HandleFunc("/tracker", wsTracker.HandleWS).Methods("GET") // WebSocket upgrade r.HandleFunc("/tracker", wsTracker.HandleWS).Methods("GET") // WebSocket upgrade
log.Printf("Registered BitTorrent tracker endpoints with WebSocket support") log.Printf("Registered BitTorrent tracker endpoints with WebSocket support")
return wsTracker
} }
// GetGatewayFromRoutes returns a gateway instance for DHT integration // GetGatewayFromRoutes returns a gateway instance for DHT integration
@ -4244,7 +4314,15 @@ func (g *Gateway) generateWebTorrentMagnet(metadata *FileMetadata) string {
"wss://tracker.btorrent.xyz", "wss://tracker.btorrent.xyz",
"wss://tracker.openwebtorrent.com", "wss://tracker.openwebtorrent.com",
"wss://tracker.webtorrent.dev", "wss://tracker.webtorrent.dev",
fmt.Sprintf("wss://localhost:%d/tracker", g.config.Gateway.Port), // Our WebSocket tracker }
// Add our WebSocket tracker using public URL
if g.publicURL != "" && g.publicURL != "http://localhost" {
// Extract domain from public URL and create WebSocket URL
publicURL := strings.TrimPrefix(g.publicURL, "http://")
publicURL = strings.TrimPrefix(publicURL, "https://")
publicDomain := strings.Split(publicURL, ":")[0] // Remove port if present
wsTrackers = append(wsTrackers, fmt.Sprintf("wss://%s/tracker", publicDomain))
} }
for _, tracker := range wsTrackers { for _, tracker := range wsTrackers {

View File

@ -47,6 +47,22 @@ type NodeInfo struct {
Reputation int `json:"reputation"` Reputation int `json:"reputation"`
} }
// GetNode returns the underlying DHT node instance
func (db *DHTBootstrap) GetNode() *DHT {
return db.node
}
// GetStats returns DHT bootstrap statistics
func (d *DHTBootstrap) GetStats() map[string]interface{} {
d.mutex.RLock()
defer d.mutex.RUnlock()
return map[string]interface{}{
"routing_table_size": len(d.knownNodes),
"torrents": len(d.torrents),
"bootstrap_nodes": len(d.config.BootstrapNodes),
}
}
// TorrentAnnounce represents a DHT torrent announcement // TorrentAnnounce represents a DHT torrent announcement
type TorrentAnnounce struct { type TorrentAnnounce struct {
InfoHash string `json:"info_hash"` InfoHash string `json:"info_hash"`

View File

@ -243,4 +243,25 @@ func (wt *WebSocketTracker) cleanupExpiredPeers() {
} }
swarm.mu.Unlock() swarm.mu.Unlock()
} }
}
// GetStats returns WebSocket tracker statistics
func (wt *WebSocketTracker) GetStats() map[string]interface{} {
wt.mu.RLock()
defer wt.mu.RUnlock()
totalPeers := 0
totalSwarms := len(wt.swarms)
for _, swarm := range wt.swarms {
swarm.mu.RLock()
totalPeers += len(swarm.peers)
swarm.mu.RUnlock()
}
return map[string]interface{}{
"total_swarms": totalSwarms,
"total_peers": totalPeers,
"status": "active",
}
} }

View File

@ -1612,16 +1612,23 @@
async function shareFile(hash) { async function shareFile(hash) {
console.log('DEBUG: shareFile function called!', hash); console.log('DEBUG: shareFile function called!', hash);
console.log('userFiles array:', userFiles);
const file = userFiles.find(f => f.hash === hash); const file = userFiles.find(f => f.hash === hash);
console.log('Found file:', file);
if (!file) { if (!file) {
console.error('File not found in userFiles array'); console.error('File not found in userFiles array');
return; return;
} }
const baseUrl = window.location.origin; const baseUrl = window.location.origin;
const magnetHash = file.torrent_info?.InfoHash || hash;
// Use torrent info hash for magnet if available, otherwise use file hash
let magnetHash = hash;
if (file.torrent_info_hash) {
magnetHash = file.torrent_info_hash;
console.log('Using torrent InfoHash for magnet:', magnetHash);
} else {
console.log('No torrent InfoHash found, using file hash:', magnetHash);
}
const links = { const links = {
direct: `${baseUrl}/api/download/${hash}`, direct: `${baseUrl}/api/download/${hash}`,
torrent: `${baseUrl}/api/torrent/${hash}`, torrent: `${baseUrl}/api/torrent/${hash}`,

Binary file not shown.