diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eef9eee..a50bc2e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ on: tags: - 'v**' schedule: - - cron: '50 1 * * *' + - cron: '45 1 * * *' jobs: build: diff --git a/internal/handlers/home.go b/internal/handlers/home.go index c703419..d145831 100644 --- a/internal/handlers/home.go +++ b/internal/handlers/home.go @@ -22,29 +22,28 @@ type SponsorResponse struct { } type RootResponse struct { - Version string `json:"version"` - BuiltAt string `json:"built_at"` - GitCommit string `json:"git_commit"` - Html string `json:"html"` - Dav string `json:"dav"` - Infuse string `json:"infuse"` - Logs string `json:"logs"` - UserInfo *realdebrid.User `json:"user_info"` - APITraffic uint64 `json:"traffic_from_api"` - ServedMB uint64 `json:"served_mb"` - LibrarySize int `json:"library_size"` // Number of torrents in the library - TorrentsToRepair string `json:"repair_queue"` // List of torrents in the repair queue - MemAlloc uint64 `json:"mem_alloc"` // Memory allocation in MB - TotalAlloc uint64 `json:"total_alloc"` // Total memory allocated in MB - Sys uint64 `json:"sys"` // System memory in MB - NumGC uint32 `json:"num_gc"` // Number of completed GC cycles - PID int `json:"pid"` // Process ID - Sponsor SponsorResponse `json:"sponsor_zurg"` // Sponsorship links - Config config.ZurgConfig `json:"config"` - Token string `json:"token"` - DownloadTokens []string `json:"download_tokens"` - IDsToDelete []string `json:"ids_to_delete"` - Hosts []string `json:"hosts"` + Version string `json:"version"` + BuiltAt string `json:"built_at"` + GitCommit string `json:"git_commit"` + Html string `json:"html"` + Dav string `json:"dav"` + Infuse string `json:"infuse"` + Logs string `json:"logs"` + UserInfo *realdebrid.User `json:"user_info"` + LibrarySize int `json:"library_size"` // Number of torrents in the library + TorrentsToRepair string `json:"repair_queue"` // List of torrents in the repair queue + MemAlloc uint64 `json:"mem_alloc"` // Memory allocation in MB + TotalAlloc uint64 `json:"total_alloc"` // Total memory allocated in MB + Sys uint64 `json:"sys"` // System memory in MB + NumGC uint32 `json:"num_gc"` // Number of completed GC cycles + PID int `json:"pid"` // Process ID + Sponsor SponsorResponse `json:"sponsor_zurg"` // Sponsorship links + Config config.ZurgConfig `json:"config"` + Token string `json:"token"` + DownloadTokens []string `json:"download_tokens"` + IDsToDelete []string `json:"ids_to_delete"` + Hosts []string `json:"hosts"` + TrafficServedPerAPI uint64 `json:"traffic_served_per_api"` } func (zr *Handlers) generateResponse(resp http.ResponseWriter, req *http.Request) (*RootResponse, error) { @@ -98,23 +97,22 @@ func (zr *Handlers) generateResponse(resp http.ResponseWriter, req *http.Request } return &RootResponse{ - Version: version.GetVersion(), - BuiltAt: version.GetBuiltAt(), - GitCommit: version.GetGitCommit(), - Html: fmt.Sprintf("//%s/http/", req.Host), - Dav: fmt.Sprintf("//%s/dav/", req.Host), - Infuse: fmt.Sprintf("//%s/infuse/", req.Host), - Logs: fmt.Sprintf("//%s/logs/", req.Host), - UserInfo: userInfo, - APITraffic: uint64(trafficFromAPI), - ServedMB: bToMb(zr.downloader.TotalBytes.Load()), - LibrarySize: allTorrents.Count(), - TorrentsToRepair: repairQueueStr, - MemAlloc: bToMb(mem.Alloc), - TotalAlloc: bToMb(mem.TotalAlloc), - Sys: bToMb(mem.Sys), - NumGC: mem.NumGC, - PID: os.Getpid(), + Version: version.GetVersion(), + BuiltAt: version.GetBuiltAt(), + GitCommit: version.GetGitCommit(), + Html: fmt.Sprintf("//%s/http/", req.Host), + Dav: fmt.Sprintf("//%s/dav/", req.Host), + Infuse: fmt.Sprintf("//%s/infuse/", req.Host), + Logs: fmt.Sprintf("//%s/logs/", req.Host), + UserInfo: userInfo, + TrafficServedPerAPI: uint64(trafficFromAPI), + LibrarySize: allTorrents.Count(), + TorrentsToRepair: repairQueueStr, + MemAlloc: bToMb(mem.Alloc), + TotalAlloc: bToMb(mem.TotalAlloc), + Sys: bToMb(mem.Sys), + NumGC: mem.NumGC, + PID: os.Getpid(), Sponsor: SponsorResponse{ Patreon: "https://www.patreon.com/debridmediamanager", Github: "https://github.com/sponsors/debridmediamanager", @@ -197,18 +195,6 @@ func (zr *Handlers) handleHome(resp http.ResponseWriter, req *http.Request) { response.Logs, ) - denominator := bToMb(response.APITraffic - zr.trafficOnStartup.Load()) - if denominator == 0 { - denominator = 1 - } - efficiency := response.ServedMB * 100 / denominator - - if zr.trafficOnStartup.Load() > response.APITraffic { - // it cannot be bigger than traffic logged - // so it must be a reset back to 0 - zr.trafficOnStartup.Store(0) - } - out += fmt.Sprintf(` Library Size @@ -235,16 +221,12 @@ func (zr *Handlers) handleHome(resp http.ResponseWriter, req *http.Request) { %d - Traffic Logged - %d MB (%d MB added) + Traffic Served (main token) + %d MB (%d MB since startup) - Traffic Served + Traffic Served (zurg) %d MB - - - Traffic Efficiency - %d%% (wasted %d MB) disclaimer: this assumes all RD traffic comes from this zurg instance `, response.LibrarySize, response.MemAlloc, @@ -252,11 +234,9 @@ func (zr *Handlers) handleHome(resp http.ResponseWriter, req *http.Request) { response.Sys, response.NumGC, response.PID, - bToMb(response.APITraffic), - bToMb(response.APITraffic-zr.trafficOnStartup.Load()), - response.ServedMB, - efficiency, - bToMb(response.APITraffic-zr.trafficOnStartup.Load())-response.ServedMB, + bToMb(response.TrafficServedPerAPI), // traffic served *api* + bToMb(response.TrafficServedPerAPI-zr.downloader.TrafficOnStartup.Load()), // traffic served *api* since startup + bToMb(zr.downloader.TrafficServed.Load()), // traffic served *zurg* ) out += fmt.Sprintf(` diff --git a/internal/handlers/router.go b/internal/handlers/router.go index b560d93..062d5b9 100644 --- a/internal/handlers/router.go +++ b/internal/handlers/router.go @@ -6,7 +6,6 @@ import ( "net/url" "path/filepath" "strings" - "sync/atomic" "github.com/debridmediamanager/zurg/internal/config" "github.com/debridmediamanager/zurg/internal/dav" @@ -21,14 +20,13 @@ import ( ) type Handlers struct { - downloader *universal.Downloader - torMgr *torrent.TorrentManager - cfg config.ConfigInterface - rd *realdebrid.RealDebrid - workerPool *ants.Pool - hosts []string - trafficOnStartup atomic.Uint64 - log *logutil.Logger + downloader *universal.Downloader + torMgr *torrent.TorrentManager + cfg config.ConfigInterface + rd *realdebrid.RealDebrid + workerPool *ants.Pool + hosts []string + log *logutil.Logger } func init() { @@ -48,16 +46,6 @@ func AttachHandlers(router *chi.Mux, downloader *universal.Downloader, torMgr *t log: log, } - trafficDetails, err := rd.GetTrafficDetails() - if err != nil { - log.Errorf("Failed to get traffic details: %v", err) - trafficDetails = make(map[string]int64) - } - hs.trafficOnStartup.Store(uint64(0)) - if _, ok := trafficDetails["real-debrid.com"]; ok { - hs.trafficOnStartup.Store(uint64(trafficDetails["real-debrid.com"])) - } - if cfg.GetUsername() != "" { router.Use(hs.basicAuth) } diff --git a/internal/universal/downloader.go b/internal/universal/downloader.go index 2cc8da9..eb11973 100644 --- a/internal/universal/downloader.go +++ b/internal/universal/downloader.go @@ -2,6 +2,7 @@ package universal import ( "context" + "fmt" "io" "net/http" "path/filepath" @@ -18,16 +19,28 @@ import ( ) type Downloader struct { - rd *realdebrid.RealDebrid - workerPool *ants.Pool - TotalBytes atomic.Uint64 + rd *realdebrid.RealDebrid + workerPool *ants.Pool + TrafficServed atomic.Uint64 + TrafficOnStartup atomic.Uint64 } func NewDownloader(rd *realdebrid.RealDebrid, workerPool *ants.Pool) *Downloader { - return &Downloader{ + dl := &Downloader{ rd: rd, workerPool: workerPool, } + + trafficDetails, err := dl.rd.GetTrafficDetails() + if err != nil { + trafficDetails = make(map[string]int64) + } + dl.TrafficOnStartup.Store(uint64(0)) + if _, ok := trafficDetails["real-debrid.com"]; ok { + dl.TrafficOnStartup.Store(uint64(trafficDetails["real-debrid.com"])) + } + + return dl } // StartResetBandwidthCountersJob is a permanent job that resets the bandwidth counters at 12AM CET @@ -48,7 +61,8 @@ func (dl *Downloader) StartResetBandwidthCountersJob() { ticker := time.NewTicker(24 * time.Hour) for { dl.rd.TokenManager.ResetAllTokens() - dl.TotalBytes.Store(0) + dl.TrafficServed.Store(0) + dl.TrafficOnStartup.Store(0) <-ticker.C } }) @@ -162,7 +176,12 @@ func (dl *Downloader) streamFileToResponse( // Add the range header if it exists if req.Header.Get("Range") != "" { - dlReq.Header.Add("Range", req.Header.Get("Range")) + rangeVal := req.Header.Get("Range") + // check if open-ended range request (e.g. "bytes=999-") + if strings.HasSuffix(rangeVal, "-") { + rangeVal += fmt.Sprintf("%d", unrestrict.Filesize-1) + } + dlReq.Header.Add("Range", rangeVal) } downloadResp, err := dl.rd.DownloadFile(dlReq) @@ -227,13 +246,14 @@ func (dl *Downloader) streamFileToResponse( n, _ = io.Copy(resp, downloadResp.Body) } + if !strings.HasPrefix(unrestrict.Link, "https://real-debrid.com/d/") { + return + } + dl.workerPool.Submit(func() { - if n == 0 { - return - } - // Update the download statistics - dl.TotalBytes.Add(uint64(n)) - if cfg.ShouldLogRequests() { + dl.TrafficServed.Add(uint64(n)) + + if cfg.ShouldLogRequests() && bToMb(uint64(n)) > 0 { log.Debugf("Served %d MB of file %s (range=%s)", bToMb(uint64(n)), unrestrict.Filename, req.Header.Get("Range")) } }) diff --git a/pkg/realdebrid/api.go b/pkg/realdebrid/api.go index ca17062..65c9b6e 100644 --- a/pkg/realdebrid/api.go +++ b/pkg/realdebrid/api.go @@ -70,6 +70,10 @@ func (rd *RealDebrid) UnrestrictAndVerify(link string) (*Download, error) { tokenMap, _ := rd.UnrestrictMap.Get(token) if tokenMap.Has(link) { download, _ := tokenMap.Get(link) + if !rd.cfg.ShouldServeFromRclone() { + return download, nil + } + // check if the link is in the verified links cache if expiry, ok := rd.verifiedLinks.Get(download.ID); ok && expiry > time.Now().Unix() { return download, nil @@ -98,17 +102,19 @@ func (rd *RealDebrid) UnrestrictAndVerify(link string) (*Download, error) { tokenMap.Set(link, download) - err = rd.downloadClient.VerifyLink(download.Download) - if utils.IsBytesLimitReached(err) { - rd.TokenManager.SetTokenAsExpired(token, "bandwidth limit exceeded") - continue - } else if utils.IsInvalidDownloadCode(err) { - continue - } else if err != nil { - return nil, err - } + if rd.cfg.ShouldServeFromRclone() { + err = rd.downloadClient.VerifyLink(download.Download) + if utils.IsBytesLimitReached(err) { + rd.TokenManager.SetTokenAsExpired(token, "bandwidth limit exceeded") + continue + } else if utils.IsInvalidDownloadCode(err) { + continue + } else if err != nil { + return nil, err + } - rd.verifiedLinks.Set(download.ID, time.Now().Unix()+DOWNLOAD_LINK_EXPIRY) + rd.verifiedLinks.Set(download.ID, time.Now().Unix()+DOWNLOAD_LINK_EXPIRY) + } return download, err }