diff --git a/main.go b/main.go index f51079c..8aff7dd 100644 --- a/main.go +++ b/main.go @@ -6,8 +6,6 @@ import ( "github.com/sirrobot01/decypharr/cmd/decypharr" "github.com/sirrobot01/decypharr/internal/config" "log" - "net/http" - _ "net/http/pprof" "os" "os/signal" "runtime/debug" @@ -27,15 +25,6 @@ func main() { config.SetConfigPath(configPath) config.Get() - if os.Getenv("ENABLE_PPROF") == "true" { - go func() { - log.Println("Starting pprof server on :6060") - if err := http.ListenAndServe(":6060", nil); err != nil { - log.Printf("pprof server error: %v", err) - } - }() - } - // Create a context canceled on SIGINT/SIGTERM ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() diff --git a/pkg/debrid/providers/realdebrid/realdebrid.go b/pkg/debrid/providers/realdebrid/realdebrid.go index 5b12cde..57bf835 100644 --- a/pkg/debrid/providers/realdebrid/realdebrid.go +++ b/pkg/debrid/providers/realdebrid/realdebrid.go @@ -511,7 +511,7 @@ func (r *RealDebrid) CheckStatus(t *types.Torrent) (*types.Torrent, error) { if status == "waiting_files_selection" { t.Files = r.getTorrentFiles(t, data) if len(t.Files) == 0 { - return t, fmt.Errorf("no video files found") + return t, fmt.Errorf("no valid files found") } filesId := make([]string, 0) for _, f := range t.Files { diff --git a/pkg/debrid/store/download_link.go b/pkg/debrid/store/download_link.go index 3b48767..b18e9a3 100644 --- a/pkg/debrid/store/download_link.go +++ b/pkg/debrid/store/download_link.go @@ -3,7 +3,6 @@ package store import ( "errors" "fmt" - "github.com/sirrobot01/decypharr/internal/utils" "github.com/sirrobot01/decypharr/pkg/debrid/types" ) @@ -103,10 +102,8 @@ func (c *Cache) fetchDownloadLink(torrentName, filename, fileLink string) (*type } c.logger.Trace().Msgf("Getting download link for %s(%s)", filename, file.Link) - downloadLink, err := c.client.GetDownloadLink(ct.Torrent, &file) if err != nil { - if errors.Is(err, utils.HosterUnavailableError) { c.logger.Trace(). Str("filename", filename). @@ -130,6 +127,7 @@ func (c *Cache) fetchDownloadLink(torrentName, filename, fileLink string) (*type if downloadLink == nil { return nil, fmt.Errorf("download link is empty after retry") } + return nil, nil } else if errors.Is(err, utils.TrafficExceededError) { // This is likely a fair usage limit error return nil, err diff --git a/pkg/debrid/store/refresh.go b/pkg/debrid/store/refresh.go index 77df74f..505ed88 100644 --- a/pkg/debrid/store/refresh.go +++ b/pkg/debrid/store/refresh.go @@ -29,12 +29,6 @@ func (fi *fileInfo) IsDir() bool { return fi.isDir } func (fi *fileInfo) ID() string { return fi.id } func (fi *fileInfo) Sys() interface{} { return nil } -type RcloneRC struct { - URL string - User string - Pass string -} - func (c *Cache) RefreshListings(refreshRclone bool) { // Copy the torrents to a string|time map c.torrents.refreshListing() // refresh torrent listings diff --git a/pkg/store/downloader.go b/pkg/store/downloader.go index fac2159..aefb950 100644 --- a/pkg/store/downloader.go +++ b/pkg/store/downloader.go @@ -153,7 +153,7 @@ func (s *Store) downloadFiles(torrent *Torrent, debridTorrent *types.Torrent, pa func (s *Store) processSymlink(torrent *Torrent, debridTorrent *types.Torrent) (string, error) { files := debridTorrent.Files if len(files) == 0 { - return "", fmt.Errorf("no video files found") + return "", fmt.Errorf("no valid files found") } s.logger.Info().Msgf("Checking symlinks for %d files...", len(files)) rCloneBase := debridTorrent.MountPath diff --git a/pkg/store/queue.go b/pkg/store/queue.go index 3b79153..f4d573e 100644 --- a/pkg/store/queue.go +++ b/pkg/store/queue.go @@ -96,7 +96,9 @@ func (s *Store) trackAvailableSlots(ctx context.Context) { return } - for _, slots := range availableSlots { + for name, slots := range availableSlots { + + s.logger.Debug().Msgf("Available slots for %s: %d", name, slots) // If slots are available, process the next import request from the queue for slots > 0 { select { diff --git a/pkg/store/torrent.go b/pkg/store/torrent.go index 6b67c57..e33fc0c 100644 --- a/pkg/store/torrent.go +++ b/pkg/store/torrent.go @@ -61,9 +61,8 @@ func (s *Store) processFiles(torrent *Torrent, debridTorrent *types.Torrent, imp _arr := importReq.Arr backoff := time.NewTimer(s.refreshInterval) defer backoff.Stop() - for debridTorrent.Status != "downloaded" { - + s.logger.Debug().Msgf("%s <- (%s) Download Progress: %.2f%%", debridTorrent.Debrid, debridTorrent.Name, debridTorrent.Progress) dbT, err := client.CheckStatus(debridTorrent) if err != nil { s.logger.Error(). @@ -90,16 +89,15 @@ func (s *Store) processFiles(torrent *Torrent, debridTorrent *types.Torrent, imp torrent = s.partialTorrentUpdate(torrent, debridTorrent) // Exit the loop for downloading statuses to prevent memory buildup - exitCondition1 := debridTorrent.Status == "downloaded" - exitCondition2 := !utils.Contains(downloadingStatuses, debridTorrent.Status) - - if exitCondition1 || exitCondition2 { + if debridTorrent.Status == "downloaded" || !utils.Contains(downloadingStatuses, debridTorrent.Status) { break } - <-backoff.C - // Increase interval gradually, cap at max - nextInterval := min(s.refreshInterval*2, 30*time.Second) - backoff.Reset(nextInterval) + select { + case <-backoff.C: + // Increase interval gradually, cap at max + nextInterval := min(s.refreshInterval*2, 30*time.Second) + backoff.Reset(nextInterval) + } } var torrentSymlinkPath string var err error @@ -117,6 +115,7 @@ func (s *Store) processFiles(torrent *Torrent, debridTorrent *types.Torrent, imp }() s.logger.Error().Err(err).Msgf("Error occured while processing torrent %s", debridTorrent.Name) importReq.markAsFailed(err, torrent, debridTorrent) + return } onSuccess := func(torrentSymlinkPath string) { diff --git a/pkg/web/assets/build/js/download.js b/pkg/web/assets/build/js/download.js index 8b43f79..caed2ff 100644 --- a/pkg/web/assets/build/js/download.js +++ b/pkg/web/assets/build/js/download.js @@ -1 +1 @@ -class DownloadManager{constructor(e){this.downloadFolder=e,this.refs={downloadForm:document.getElementById("downloadForm"),magnetURI:document.getElementById("magnetURI"),torrentFiles:document.getElementById("torrentFiles"),arr:document.getElementById("arr"),downloadAction:document.getElementById("downloadAction"),downloadUncached:document.getElementById("downloadUncached"),downloadFolder:document.getElementById("downloadFolder"),debrid:document.getElementById("debrid"),submitBtn:document.getElementById("submitDownload"),activeCount:document.getElementById("activeCount"),completedCount:document.getElementById("completedCount"),totalSize:document.getElementById("totalSize")},this.init()}init(){this.loadSavedOptions(),this.bindEvents(),this.handleMagnetFromURL()}bindEvents(){this.refs.downloadForm.addEventListener("submit",e=>this.handleSubmit(e)),this.refs.arr.addEventListener("change",()=>this.saveOptions()),this.refs.downloadAction.addEventListener("change",()=>this.saveOptions()),this.refs.downloadUncached.addEventListener("change",()=>this.saveOptions()),this.refs.downloadFolder.addEventListener("change",()=>this.saveOptions()),this.refs.torrentFiles.addEventListener("change",e=>this.handleFileSelection(e)),this.setupDragAndDrop()}loadSavedOptions(){const e={category:localStorage.getItem("downloadCategory")||"",action:localStorage.getItem("downloadAction")||"symlink",uncached:"true"===localStorage.getItem("downloadUncached"),folder:localStorage.getItem("downloadFolder")||this.downloadFolder};this.refs.arr.value=e.category,this.refs.downloadAction.value=e.action,this.refs.downloadUncached.checked=e.uncached,this.refs.downloadFolder.value=e.folder}saveOptions(){localStorage.setItem("downloadCategory",this.refs.arr.value),localStorage.setItem("downloadAction",this.refs.downloadAction.value),localStorage.setItem("downloadUncached",this.refs.downloadUncached.checked.toString()),localStorage.setItem("downloadFolder",this.refs.downloadFolder.value)}handleMagnetFromURL(){const e=new URLSearchParams(window.location.search).get("magnet");e&&(this.refs.magnetURI.value=e,history.replaceState({},document.title,window.location.pathname),window.decypharrUtils.createToast("Magnet link loaded from URL","info"))}async handleSubmit(e){e.preventDefault();const t=new FormData,r=this.refs.magnetURI.value.split("\n").map(e=>e.trim()).filter(e=>e.length>0);r.length>0&&t.append("urls",r.join("\n"));for(let e=0;e100)window.decypharrUtils.createToast("Please submit up to 100 torrents at a time","warning");else{t.append("arr",this.refs.arr.value),t.append("downloadFolder",this.refs.downloadFolder.value),t.append("action",this.refs.downloadAction.value),t.append("downloadUncached",this.refs.downloadUncached.checked),this.refs.debrid&&t.append("debrid",this.refs.debrid.value);try{window.decypharrUtils.setButtonLoading(this.refs.submitBtn,!0);const e=await window.decypharrUtils.fetcher("/api/add",{method:"POST",body:t,headers:{}}),r=await e.json();if(!e.ok)throw new Error(r.error||"Unknown error");r.errors&&r.errors.length>0?r.results.length>0?(window.decypharrUtils.createToast(`Added ${r.results.length} torrents with ${r.errors.length} errors`,"warning"),this.showErrorDetails(r.errors)):(window.decypharrUtils.createToast("Failed to add torrents","error"),this.showErrorDetails(r.errors)):(window.decypharrUtils.createToast(`Successfully added ${r.results.length} torrent${r.results.length>1?"s":""}!`),this.clearForm())}catch(e){console.error("Error adding downloads:",e),window.decypharrUtils.createToast(`Error adding downloads: ${e.message}`,"error")}finally{window.decypharrUtils.setButtonLoading(this.refs.submitBtn,!1)}}else window.decypharrUtils.createToast("Please provide at least one torrent","warning")}showErrorDetails(e){const t=e.map(e=>`• ${e}`).join("\n");console.error("Download errors:",t),setTimeout(()=>{confirm("Some torrents failed to add. Would you like to see the details?")&&alert(t)},1e3)}clearForm(){this.refs.magnetURI.value="",this.refs.torrentFiles.value=""}handleFileSelection(e){const t=e.target.files;if(t.length>0){const e=Array.from(t).map(e=>e.name).join(", ");window.decypharrUtils.createToast(`Selected ${t.length} file${t.length>1?"s":""}: ${e}`,"info")}}setupDragAndDrop(){const e=this.refs.downloadForm;["dragenter","dragover","dragleave","drop"].forEach(t=>{e.addEventListener(t,this.preventDefaults,!1)}),["dragenter","dragover"].forEach(t=>{e.addEventListener(t,()=>this.highlight(e),!1)}),["dragleave","drop"].forEach(t=>{e.addEventListener(t,()=>this.unhighlight(e),!1)}),e.addEventListener("drop",e=>this.handleDrop(e),!1)}preventDefaults(e){e.preventDefault(),e.stopPropagation()}highlight(e){e.classList.add("border-primary","border-2","border-dashed","bg-primary/5")}unhighlight(e){e.classList.remove("border-primary","border-2","border-dashed","bg-primary/5")}handleDrop(e){const t=e.dataTransfer.files,r=Array.from(t).filter(e=>e.name.toLowerCase().endsWith(".torrent"));if(r.length>0){const e=new DataTransfer;r.forEach(t=>e.items.add(t)),this.refs.torrentFiles.files=e.files,this.handleFileSelection({target:{files:r}})}else window.decypharrUtils.createToast("Please drop .torrent files only","warning")}} \ No newline at end of file +class DownloadManager{constructor(e){this.downloadFolder=e,this.refs={downloadForm:document.getElementById("downloadForm"),magnetURI:document.getElementById("magnetURI"),torrentFiles:document.getElementById("torrentFiles"),arr:document.getElementById("arr"),downloadAction:document.getElementById("downloadAction"),downloadUncached:document.getElementById("downloadUncached"),downloadFolder:document.getElementById("downloadFolder"),debrid:document.getElementById("debrid"),submitBtn:document.getElementById("submitDownload"),activeCount:document.getElementById("activeCount"),completedCount:document.getElementById("completedCount"),totalSize:document.getElementById("totalSize")},this.init()}init(){this.loadSavedOptions(),this.bindEvents(),this.handleMagnetFromURL()}bindEvents(){this.refs.downloadForm.addEventListener("submit",e=>this.handleSubmit(e)),this.refs.arr.addEventListener("change",()=>this.saveOptions()),this.refs.downloadAction.addEventListener("change",()=>this.saveOptions()),this.refs.downloadUncached.addEventListener("change",()=>this.saveOptions()),this.refs.downloadFolder.addEventListener("change",()=>this.saveOptions()),this.refs.torrentFiles.addEventListener("change",e=>this.handleFileSelection(e)),this.setupDragAndDrop()}loadSavedOptions(){const e={category:localStorage.getItem("downloadCategory")||"",action:localStorage.getItem("downloadAction")||"symlink",uncached:"true"===localStorage.getItem("downloadUncached"),folder:localStorage.getItem("downloadFolder")||this.downloadFolder};this.refs.arr.value=e.category,this.refs.downloadAction.value=e.action,this.refs.downloadUncached.checked=e.uncached,this.refs.downloadFolder.value=e.folder}saveOptions(){localStorage.setItem("downloadCategory",this.refs.arr.value),localStorage.setItem("downloadAction",this.refs.downloadAction.value),localStorage.setItem("downloadUncached",this.refs.downloadUncached.checked.toString()),localStorage.setItem("downloadFolder",this.refs.downloadFolder.value)}handleMagnetFromURL(){const e=new URLSearchParams(window.location.search).get("magnet");e&&(this.refs.magnetURI.value=e,history.replaceState({},document.title,window.location.pathname),window.decypharrUtils.createToast("Magnet link loaded from URL","info"))}async handleSubmit(e){e.preventDefault();const t=new FormData,r=this.refs.magnetURI.value.split("\n").map(e=>e.trim()).filter(e=>e.length>0);r.length>0&&t.append("urls",r.join("\n"));for(let e=0;e100)window.decypharrUtils.createToast("Please submit up to 100 torrents at a time","warning");else{t.append("arr",this.refs.arr.value),t.append("downloadFolder",this.refs.downloadFolder.value),t.append("action",this.refs.downloadAction.value),t.append("downloadUncached",this.refs.downloadUncached.checked),this.refs.debrid&&t.append("debrid",this.refs.debrid.value);try{window.decypharrUtils.setButtonLoading(this.refs.submitBtn,!0);const e=await window.decypharrUtils.fetcher("/api/add",{method:"POST",body:t,headers:{}}),r=await e.json();if(!e.ok)throw new Error(r.error||"Unknown error");r.errors&&r.errors.length>0?r.results.length>0?(window.decypharrUtils.createToast(`Added ${r.results.length} torrents with ${r.errors.length} errors`,"warning"),this.showErrorDetails(r.errors)):(window.decypharrUtils.createToast("Failed to add torrents","error"),this.showErrorDetails(r.errors)):(window.decypharrUtils.createToast(`Successfully added ${r.results.length} torrent${r.results.length>1?"s":""}!`),this.clearForm())}catch(e){console.error("Error adding downloads:",e),window.decypharrUtils.createToast(`Error adding downloads: ${e.message}`,"error")}finally{window.decypharrUtils.setButtonLoading(this.refs.submitBtn,!1)}}else window.decypharrUtils.createToast("Please provide at least one torrent","warning")}showErrorDetails(e){const t=e.map(e=>`• ${e}`).join("\n");console.error("Download errors:",t),window.decypharrUtils.createToast(`Errors occurred while adding torrents:\n${t}`,"error")}clearForm(){this.refs.magnetURI.value="",this.refs.torrentFiles.value=""}handleFileSelection(e){const t=e.target.files;if(t.length>0){const e=Array.from(t).map(e=>e.name).join(", ");window.decypharrUtils.createToast(`Selected ${t.length} file${t.length>1?"s":""}: ${e}`,"info")}}setupDragAndDrop(){const e=this.refs.downloadForm;["dragenter","dragover","dragleave","drop"].forEach(t=>{e.addEventListener(t,this.preventDefaults,!1)}),["dragenter","dragover"].forEach(t=>{e.addEventListener(t,()=>this.highlight(e),!1)}),["dragleave","drop"].forEach(t=>{e.addEventListener(t,()=>this.unhighlight(e),!1)}),e.addEventListener("drop",e=>this.handleDrop(e),!1)}preventDefaults(e){e.preventDefault(),e.stopPropagation()}highlight(e){e.classList.add("border-primary","border-2","border-dashed","bg-primary/5")}unhighlight(e){e.classList.remove("border-primary","border-2","border-dashed","bg-primary/5")}handleDrop(e){const t=e.dataTransfer.files,r=Array.from(t).filter(e=>e.name.toLowerCase().endsWith(".torrent"));if(r.length>0){const e=new DataTransfer;r.forEach(t=>e.items.add(t)),this.refs.torrentFiles.files=e.files,this.handleFileSelection({target:{files:r}})}else window.decypharrUtils.createToast("Please drop .torrent files only","warning")}} \ No newline at end of file diff --git a/pkg/web/assets/js/download.js b/pkg/web/assets/js/download.js index c3d1663..411cae0 100644 --- a/pkg/web/assets/js/download.js +++ b/pkg/web/assets/js/download.js @@ -166,13 +166,10 @@ class DownloadManager { // Create a modal or detailed view for errors const errorList = errors.map(error => `• ${error}`).join('\n'); console.error('Download errors:', errorList); - - // You could also show this in a modal for better UX - setTimeout(() => { - if (confirm('Some torrents failed to add. Would you like to see the details?')) { - alert(errorList); - } - }, 1000); + window.decypharrUtils.createToast( + `Errors occurred while adding torrents:\n${errorList}`, + 'error' + ); } clearForm() { diff --git a/pkg/webdav/webdav.go b/pkg/webdav/webdav.go index 2060455..1b6e387 100644 --- a/pkg/webdav/webdav.go +++ b/pkg/webdav/webdav.go @@ -157,7 +157,7 @@ func (wd *WebDav) mountHandlers(r chi.Router) { r.Route("/"+h.Name, func(r chi.Router) { r.Use(h.readinessMiddleware) r.Mount("/", h) - }) + }) // Mount to /name since router is already prefixed with /webdav } }