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
apiRouter := r.PathPrefix("/api").Subrouter()
// Register tracker routes on main router (no /api prefix for BitTorrent compatibility)
api.RegisterTrackerRoutes(r, cfg, storageBackend)
// Register main API routes and get gateway instance
// Register main API routes and get gateway instance first
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
webFS := web.GetFS()
staticFS, _ := fs.Sub(webFS, "static")

View File

@ -15,6 +15,21 @@ import (
"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
type GatewayInterface interface {
GetDB() *sql.DB
@ -23,6 +38,9 @@ type GatewayInterface interface {
CleanupOrphanedChunks() (map[string]interface{}, error)
CleanupInactiveUsers(days int) (map[string]interface{}, error)
ReconstructTorrentFile(fileHash, fileName string) (string, error)
GetTrackerInstance() TrackerInterface
GetWebSocketTracker() WebSocketTrackerInterface
GetDHTNode() DHTInterface
}
// 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{} {
p2p := make(map[string]interface{})
// P2P health score calculation (mock implementation)
p2p["health_score"] = 87
p2p["dht_node_count"] = 1249
p2p["webseed_hit_ratio"] = "91.2%"
p2p["torrent_completion_avg"] = "2.1 mins"
// Get real P2P stats from tracker instance
if tracker := ah.gateway.GetTrackerInstance(); tracker != nil {
trackerStats := tracker.GetStats()
if peers, ok := trackerStats["peers"]; ok {
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
}
@ -1256,12 +1298,14 @@ func (ah *AdminHandlers) gatherP2PHealthMetrics() map[string]interface{} {
func (ah *AdminHandlers) gatherWebTorrentStats() map[string]interface{} {
webtorrent := make(map[string]interface{})
// WebTorrent peer statistics (mock data)
webtorrent["peers_active"] = 45
webtorrent["browser_client_types"] = map[string]int{
"Chrome": 60,
"Firefox": 25,
"Safari": 15,
// Get real WebSocket tracker stats (WebTorrent uses WebSocket tracker)
if wsTracker := ah.gateway.GetWebSocketTracker(); wsTracker != nil {
wsStats := wsTracker.GetStats()
webtorrent["active_swarms"] = wsStats["swarms"]
webtorrent["connected_peers"] = wsStats["peers"]
} else {
webtorrent["active_swarms"] = 0
webtorrent["connected_peers"] = 0
}
return webtorrent

View File

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

View File

@ -112,6 +112,8 @@ type Gateway struct {
publicURL string
trackerInstance *tracker.Tracker
dhtBootstrap DHTBootstrap
dhtNode *dht.DHTBootstrap // Add actual DHT instance
wsTracker *tracker.WebSocketTracker // Add WebSocket tracker instance
transcodingManager TranscodingManager
}
@ -330,11 +332,14 @@ type ChunkInfo struct {
}
type UploadResponse struct {
FileHash string `json:"file_hash"`
Message string `json:"message"`
TorrentHash string `json:"torrent_hash,omitempty"`
MagnetLink string `json:"magnet_link,omitempty"`
FileHash string `json:"file_hash"`
Message string `json:"message"`
TorrentHash string `json:"torrent_hash,omitempty"`
MagnetLink string `json:"magnet_link,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 {
@ -641,6 +646,39 @@ func (g *Gateway) GetAllTorrentHashes() []string {
// SetDHTBootstrap sets the DHT bootstrap instance for torrent announcements
func (g *Gateway) SetDHTBootstrap(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) {
@ -818,6 +856,7 @@ func (g *Gateway) handleBlobUpload(w http.ResponseWriter, r *http.Request, file
// Publish to Nostr for blobs
var nostrEventID string
var nip71EventID string
if g.nostrPublisher != nil {
eventData := nostr.TorrentEventData{
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)
} else {
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,
Message: "File uploaded successfully as blob",
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")
@ -1052,6 +1099,7 @@ func (g *Gateway) handleTorrentUpload(w http.ResponseWriter, r *http.Request, fi
// Publish to Nostr
var nostrEventID string
var nip71EventID string
if g.nostrPublisher != nil {
eventData := nostr.TorrentEventData{
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)
} else {
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,
MagnetLink: torrentInfo.Magnet,
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")
@ -2247,24 +2303,37 @@ func (g *Gateway) P2PStatsHandler(w http.ResponseWriter, r *http.Request) {
stats := make(map[string]interface{})
// Tracker statistics
// BitTorrent tracker statistics - get real stats from tracker instance
if g.trackerInstance != nil {
trackerStats := make(map[string]interface{})
trackerStats["status"] = "active"
trackerStats["uptime_seconds"] = time.Since(time.Now()).Seconds() // Placeholder
stats["tracker"] = trackerStats
stats["tracker"] = g.trackerInstance.GetStats()
}
// DHT statistics
if g.dhtBootstrap != nil {
dhtStats := make(map[string]interface{})
dhtStats["status"] = "active"
dhtStats["routing_table_size"] = "N/A" // Would need DHT interface methods
dhtStats["active_searches"] = 0
dhtStats["stored_values"] = 0
// WebSocket tracker statistics - get real stats from WebSocket tracker
if g.wsTracker != nil {
stats["websocket_tracker"] = g.wsTracker.GetStats()
}
stats["dht"] = dhtStats
// DHT statistics - get real stats from DHT node
if g.dhtNode != nil {
// Get stats from the actual DHT node within the bootstrap
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)
@ -2302,7 +2371,8 @@ func (g *Gateway) P2PStatsHandler(w http.ResponseWriter, r *http.Request) {
stats["coordination"] = map[string]interface{}{
"integration_active": g.trackerInstance != nil && g.dhtBootstrap != nil,
"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),
}
@ -4141,12 +4211,11 @@ func systemStatsHandler(gateway *Gateway, storage *storage.Backend, trackerInsta
}
// 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") {
return
return nil
}
gateway := NewGateway(cfg, storage)
trackerInstance := tracker.NewTracker(&cfg.Tracker, gateway)
announceHandler := tracker.NewAnnounceHandler(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
log.Printf("Registered BitTorrent tracker endpoints with WebSocket support")
return wsTracker
}
// 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.openwebtorrent.com",
"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 {

View File

@ -47,6 +47,22 @@ type NodeInfo struct {
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
type TorrentAnnounce struct {
InfoHash string `json:"info_hash"`

View File

@ -244,3 +244,24 @@ func (wt *WebSocketTracker) cleanupExpiredPeers() {
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) {
console.log('DEBUG: shareFile function called!', hash);
console.log('userFiles array:', userFiles);
const file = userFiles.find(f => f.hash === hash);
console.log('Found file:', file);
if (!file) {
console.error('File not found in userFiles array');
return;
}
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 = {
direct: `${baseUrl}/api/download/${hash}`,
torrent: `${baseUrl}/api/torrent/${hash}`,

Binary file not shown.