diff --git a/internal/config/types.go b/internal/config/types.go index 59a9875..23c0cbd 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -29,20 +29,22 @@ type ConfigInterface interface { EnableDownloadMount() bool GetRateLimitSleepSecs() int ShouldDeleteRarFiles() bool + GetDownloadsEveryMins() int } type ZurgConfig struct { Version string `yaml:"zurg" json:"-"` Token string `yaml:"token" json:"-"` - Host string `yaml:"host" json:"host"` - Port string `yaml:"port" json:"port"` - Username string `yaml:"username" json:"username"` - Password string `yaml:"password" json:"password"` - Proxy string `yaml:"proxy" json:"proxy"` - NumOfWorkers int `yaml:"concurrent_workers" json:"concurrent_workers"` - RefreshEverySecs int `yaml:"check_for_changes_every_secs" json:"check_for_changes_every_secs"` - RepairEveryMins int `yaml:"repair_every_mins" json:"repair_every_mins"` + Host string `yaml:"host" json:"host"` + Port string `yaml:"port" json:"port"` + Username string `yaml:"username" json:"username"` + Password string `yaml:"password" json:"password"` + Proxy string `yaml:"proxy" json:"proxy"` + NumOfWorkers int `yaml:"concurrent_workers" json:"concurrent_workers"` + RefreshEverySecs int `yaml:"check_for_changes_every_secs" json:"check_for_changes_every_secs"` + RepairEveryMins int `yaml:"repair_every_mins" json:"repair_every_mins"` + DownloadsEveryMins int `yaml:"downloads_every_mins" json:"downloads_every_mins"` IgnoreRenames bool `yaml:"ignore_renames" json:"ignore_renames"` RetainRDTorrentName bool `yaml:"retain_rd_torrent_name" json:"retain_rd_torrent_name"` @@ -119,6 +121,13 @@ func (z *ZurgConfig) GetRepairEveryMins() int { return z.RepairEveryMins } +func (z *ZurgConfig) GetDownloadsEveryMins() int { + if z.DownloadsEveryMins == 0 { + return 60 + } + return z.DownloadsEveryMins +} + func (z *ZurgConfig) EnableRepair() bool { return z.CanRepair } diff --git a/internal/handlers/home.go b/internal/handlers/home.go index 6d58c2e..3535297 100644 --- a/internal/handlers/home.go +++ b/internal/handlers/home.go @@ -68,185 +68,204 @@ func (zr *Handlers) handleHome(resp http.ResponseWriter, req *http.Request) { } out := ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - - - - - + + + + - - - + + + - - - + + + - - - + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - + + + + + + + + + + + + + + + + +
zurg
Version%s
Built At%s
Git Commit%s
HTML%s
DAV%s
Infuse%s
Logs%s
Memory Allocation%d MB
Total Memory Allocated%d MB
System Memory%d MB
Number of GC Cycles%d
Process ID%d
Sponsor ZurgPatreon
zurg
Version%s
Built At%s
Git Commit%s
HTML%s
DAV%s
Infuse%s
Logs%s
Memory Allocation%d MB
Total Memory Allocated%d MB
System Memory%d MB
Number of GC Cycles%d
Process ID%d
Sponsor ZurgPatreon %s
Github
Github %s
Paypal
Paypal %s
User InfoUsername
User InfoUsername %s
Points
Points %d
Locale
Locale %s
Type
Type %s
Premium
Premium %d days
Expiration
Expiration %s
ConfigVersion%s
Token
ConfigVersion %s
Host
Token %s
Port
Host %s
Workers
Port%s
Workers %d running / %d free / %d total
Refresh Every...
Refresh Every... %d secs
Retain RD Torrent Name
Retain RD Torrent Name %t
Retain Folder Name Extension
Retain Folder Name Extension %t
Can Repair
Can Repair %t
Delete Rar Files
Repair Every...%d mins
Delete Rar Files %t
API Timeout
API Timeout %d secs
Download Timeout
Download Timeout %d secs
Use Download Mount
Use Download Mount %t
Rate Limit Sleep for...
Refresh Download Mount Every...%d mins
Rate Limit Sleep for... %d secs
Retries Until Failed
Retries Until Failed %d
Network Buffer Size
Network Buffer Size %d bytes
Serve From Rclone
Serve From Rclone %t
Verify Download Link
Verify Download Link %t
Force IPv6
Force IPv6 %t
On Library Update
On Library Update %s
Utilities
Utilities - - - - -
` out = fmt.Sprintf(out, @@ -289,10 +308,12 @@ func (zr *Handlers) handleHome(resp http.ResponseWriter, req *http.Request) { response.Config.EnableRetainRDTorrentName(), response.Config.EnableRetainFolderNameExtension(), response.Config.EnableRepair(), + response.Config.GetRepairEveryMins(), response.Config.ShouldDeleteRarFiles(), response.Config.GetApiTimeoutSecs(), response.Config.GetDownloadTimeoutSecs(), response.Config.EnableDownloadMount(), + response.Config.GetDownloadsEveryMins(), response.Config.GetRateLimitSleepSecs(), response.Config.GetRetriesUntilFailed(), response.Config.GetNetworkBufferSize(), @@ -306,24 +327,34 @@ func (zr *Handlers) handleHome(resp http.ResponseWriter, req *http.Request) { } func (zr *Handlers) handleRebootWorkerPool(resp http.ResponseWriter, req *http.Request) { - // zr.workerPool.Release() + resp.Header().Set("Refresh", "2; url=/") + zr.workerPool.Release() zr.workerPool.Reboot() zr.log.Infof("Rebooted worker pool") - fmt.Fprint(resp, "Rebooted worker pool, please close this window") + fmt.Fprint(resp, "Rebooting worker pool...") } -func (zr *Handlers) handleRebootRefreshPool(resp http.ResponseWriter, req *http.Request) { +func (zr *Handlers) handleRebootRefreshWorker(resp http.ResponseWriter, req *http.Request) { + resp.Header().Set("Refresh", "2; url=/") zr.torMgr.RefreshKillSwitch <- struct{}{} zr.torMgr.StartRefreshJob() zr.log.Infof("Rebooted refresh worker") - fmt.Fprint(resp, "Rebooted refresh worker, please close this window") + fmt.Fprint(resp, "Rebooting refresh worker...") } -func (zr *Handlers) handleRebootRepairPool(resp http.ResponseWriter, req *http.Request) { +func (zr *Handlers) handleRebootRepairWorker(resp http.ResponseWriter, req *http.Request) { + resp.Header().Set("Refresh", "2; url=/") zr.torMgr.RepairKillSwitch <- struct{}{} zr.torMgr.StartRepairJob() zr.log.Infof("Rebooted repair worker") - fmt.Fprint(resp, "Rebooted repair worker, please close this window") + fmt.Fprint(resp, "Rebooting repair worker...") +} + +func (zr *Handlers) handleRemountDownloads(resp http.ResponseWriter, req *http.Request) { + resp.Header().Set("Refresh", "2; url=/") + zr.torMgr.RemountTrigger <- struct{}{} + zr.log.Infof("Triggered remount of downloads") + fmt.Fprint(resp, "Remounting downloads...") } func bToMb(b uint64) uint64 { diff --git a/internal/handlers/router.go b/internal/handlers/router.go index 8668100..a4162a5 100644 --- a/internal/handlers/router.go +++ b/internal/handlers/router.go @@ -20,14 +20,12 @@ import ( ) type Handlers struct { - downloader *universal.Downloader - torMgr *torrent.TorrentManager - cfg config.ConfigInterface - api *realdebrid.RealDebrid - workerPool *ants.Pool - refreshPool *ants.Pool - repairPool *ants.Pool - log *logutil.Logger + downloader *universal.Downloader + torMgr *torrent.TorrentManager + cfg config.ConfigInterface + api *realdebrid.RealDebrid + workerPool *ants.Pool + log *logutil.Logger } func init() { @@ -52,9 +50,10 @@ func AttachHandlers(router *chi.Mux, downloader *universal.Downloader, torMgr *t router.Use(hs.options) router.Get("/", hs.handleHome) - router.Get("/reboot/worker", hs.handleRebootWorkerPool) - router.Get("/reboot/refresh", hs.handleRebootRefreshPool) - router.Get("/reboot/repair", hs.handleRebootRepairPool) + router.Post("/reboot/worker", hs.handleRebootWorkerPool) + router.Post("/reboot/refresh", hs.handleRebootRefreshWorker) + router.Post("/reboot/repair", hs.handleRebootRepairWorker) + router.Post("/remount/downloads", hs.handleRemountDownloads) // version router.Get(fmt.Sprintf("/{mountType}/%s", version.FILE), hs.handleVersionFile) router.Head(fmt.Sprintf("/{mountType}/%s", version.FILE), hs.handleCheckVersionFile) diff --git a/internal/torrent/manager.go b/internal/torrent/manager.go index 30cd5f6..84f301c 100644 --- a/internal/torrent/manager.go +++ b/internal/torrent/manager.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" "sync" + "time" "github.com/debridmediamanager/zurg/internal/config" "github.com/debridmediamanager/zurg/pkg/logutil" @@ -33,6 +34,7 @@ type TorrentManager struct { workerPool *ants.Pool RefreshKillSwitch chan struct{} RepairKillSwitch chan struct{} + RemountTrigger chan struct{} repairTrigger chan *Torrent repairSet mapset.Set[*Torrent] repairRunning bool @@ -49,9 +51,9 @@ func NewTorrentManager(cfg config.ConfigInterface, api *realdebrid.RealDebrid, w Api: api, DirectoryMap: cmap.New[cmap.ConcurrentMap[string, *Torrent]](), DownloadCache: cmap.New[*realdebrid.Download](), - DownloadMap: cmap.New[*realdebrid.Download](), RefreshKillSwitch: make(chan struct{}, 1), RepairKillSwitch: make(chan struct{}, 1), + RemountTrigger: make(chan struct{}, 1), allAccessKeys: mapset.NewSet[string](), latestState: &LibraryState{}, requiredVersion: "0.9.3-hotfix.4", @@ -60,11 +62,12 @@ func NewTorrentManager(cfg config.ConfigInterface, api *realdebrid.RealDebrid, w } t.fixers = t.readFixersFromFile() t.initializeDirectories() - t.mountDownloads() t.refreshTorrents() t.setNewLatestState(t.getCurrentState()) t.StartRefreshJob() t.StartRepairJob() + t.mountDownloads() + t.StartDownloadsJob() return t } @@ -85,7 +88,9 @@ func (t *TorrentManager) UnrestrictUntilOk(link string) *realdebrid.Download { } if ret != nil && ret.Link != "" && ret.Filename != "" { t.DownloadCache.Set(ret.Link, ret) - t.DownloadMap.Set(ret.Filename, ret) + if t.Config.EnableDownloadMount() { + t.DownloadMap.Set(ret.Filename, ret) + } } return ret } @@ -179,6 +184,7 @@ func (t *TorrentManager) mountDownloads() { if !t.Config.EnableDownloadMount() { return } + t.DownloadMap = cmap.New[*realdebrid.Download]() _ = t.workerPool.Submit(func() { page := 1 offset := 0 @@ -196,7 +202,23 @@ func (t *TorrentManager) mountDownloads() { break } } - t.log.Infof("Compiled into %d downloads", t.DownloadCache.Count()) + t.log.Infof("Compiled into %d downloads", t.DownloadMap.Count()) + }) +} + +func (t *TorrentManager) StartDownloadsJob() { + _ = t.workerPool.Submit(func() { + remountTicker := time.NewTicker(time.Duration(t.Config.GetDownloadsEveryMins()) * time.Minute) + defer remountTicker.Stop() + + for { + select { + case <-remountTicker.C: + t.mountDownloads() + case <-t.RemountTrigger: + t.mountDownloads() + } + } }) } diff --git a/internal/universal/downloader.go b/internal/universal/downloader.go index 2e0dc9a..ff0548a 100644 --- a/internal/universal/downloader.go +++ b/internal/universal/downloader.go @@ -104,19 +104,6 @@ func (dl *Downloader) DownloadLink(fileName, link string, resp http.ResponseWrit http.Error(resp, "File is not available", http.StatusInternalServerError) return } else { - lFilename := strings.ToLower(fileName) - unrestrictFilename := strings.ToLower(strings.TrimPrefix(unrestrict.Filename, "/")) - if strings.Contains(lFilename, unrestrictFilename) { - // this is possible if there's only 1 streamable file in the torrent - // and then suddenly it's a rar file - actualExt := filepath.Ext(unrestrictFilename) - expectedExt := filepath.Ext(lFilename) - if actualExt != expectedExt && unrestrict.Streamable != 1 { - log.Warnf("File was changed and is not streamable: %s and %s (link=%s)", fileName, unrestrict.Filename, unrestrict.Link) - } else { - log.Warnf("Filename mismatch: %s and %s", fileName, unrestrict.Filename) - } - } if cfg.ShouldServeFromRclone() { if cfg.ShouldVerifyDownloadLink() { if !dl.client.CanFetchFirstByte(unrestrict.Download) {