diff --git a/CHANGELOG.md b/CHANGELOG.md index 5754c17..9843c7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,4 +96,13 @@ #### 0.3.1 - Add DebridLink Support -- Refactor error handling \ No newline at end of file +- Refactor error handling + +#### 0.3.2 + +- Fix DebridLink not downloading +- Fix Torbox with uncached torrents +- Add new /internal/cached endpoint to check if an hash is cached +- implement per-debrid local cache +- Fix file check for torbox +- Other minor bug fixes \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go index 73920ca..63bb785 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -12,9 +12,8 @@ import ( func Start(ctx context.Context, config *common.Config) error { maxCacheSize := cmp.Or(config.MaxCacheSize, 1000) - cache := common.NewCache(maxCacheSize) - deb := debrid.NewDebrid(config.Debrids, cache) + deb := debrid.NewDebrid(config.Debrids, maxCacheSize) var wg sync.WaitGroup errChan := make(chan error, 2) @@ -23,7 +22,7 @@ func Start(ctx context.Context, config *common.Config) error { wg.Add(1) go func() { defer wg.Done() - if err := proxy.NewProxy(*config, deb, cache).Start(ctx); err != nil { + if err := proxy.NewProxy(*config, deb).Start(ctx); err != nil { errChan <- err } }() @@ -32,7 +31,7 @@ func Start(ctx context.Context, config *common.Config) error { wg.Add(1) go func() { defer wg.Done() - if err := qbit.Start(ctx, config, deb, cache); err != nil { + if err := qbit.Start(ctx, config, deb); err != nil { errChan <- err } }() diff --git a/common/cache.go b/common/cache.go index ba79f45..e3b42ee 100644 --- a/common/cache.go +++ b/common/cache.go @@ -40,14 +40,16 @@ func (c *Cache) AddMultiple(values map[string]bool) { c.mu.Lock() defer c.mu.Unlock() - for value := range values { - if _, exists := c.data[value]; !exists { - if len(c.order) >= c.maxItems { - delete(c.data, c.order[0]) - c.order = c.order[1:] + for value, exists := range values { + if !exists { + if _, exists := c.data[value]; !exists { + if len(c.order) >= c.maxItems { + delete(c.data, c.order[0]) + c.order = c.order[1:] + } + c.data[value] = struct{}{} + c.order = append(c.order, value) } - c.data[value] = struct{}{} - c.order = append(c.order, value) } } } diff --git a/pkg/debrid/debrid.go b/pkg/debrid/debrid.go index 2672a13..db10aad 100644 --- a/pkg/debrid/debrid.go +++ b/pkg/debrid/debrid.go @@ -34,10 +34,13 @@ type Service interface { GetLogger() *log.Logger } -func NewDebrid(debs []common.DebridConfig, cache *common.Cache) *DebridService { +func NewDebrid(debs []common.DebridConfig, maxCachedSize int) *DebridService { debrids := make([]Service, 0) + // Divide the cache size by the number of debrids + maxCacheSize := maxCachedSize / len(debs) + for _, dc := range debs { - d := createDebrid(dc, cache) + d := createDebrid(dc, common.NewCache(maxCacheSize)) d.GetLogger().Println("Debrid Service started") debrids = append(debrids, d) } @@ -114,26 +117,27 @@ func getTorrentInfo(filePath string) (*Torrent, error) { func GetLocalCache(infohashes []string, cache *common.Cache) ([]string, map[string]bool) { result := make(map[string]bool) + hashes := make([]string, 0) - //if len(infohashes) == 0 { - // return hashes, result - //} - //if len(infohashes) == 1 { - // if cache.Exists(infohashes[0]) { - // return hashes, map[string]bool{infohashes[0]: true} - // } - // return infohashes, result - //} - // - //cachedHashes := cache.GetMultiple(infohashes) - //for _, h := range infohashes { - // _, exists := cachedHashes[h] - // if !exists { - // hashes = append(hashes, h) - // } else { - // result[h] = true - // } - //} + if len(infohashes) == 0 { + return hashes, result + } + if len(infohashes) == 1 { + if cache.Exists(infohashes[0]) { + return hashes, map[string]bool{infohashes[0]: true} + } + return infohashes, result + } + + cachedHashes := cache.GetMultiple(infohashes) + for _, h := range infohashes { + _, exists := cachedHashes[h] + if !exists { + hashes = append(hashes, h) + } else { + result[h] = true + } + } return infohashes, result } diff --git a/pkg/debrid/debrid_link.go b/pkg/debrid/debrid_link.go index 843873a..644798c 100644 --- a/pkg/debrid/debrid_link.go +++ b/pkg/debrid/debrid_link.go @@ -39,8 +39,8 @@ func (r *DebridLink) IsAvailable(infohashes []string) map[string]bool { } // Divide hashes into groups of 100 - for i := 0; i < len(hashes); i += 200 { - end := i + 200 + for i := 0; i < len(hashes); i += 100 { + end := i + 100 if end > len(hashes) { end = len(hashes) } diff --git a/pkg/debrid/realdebrid.go b/pkg/debrid/realdebrid.go index 413dfd1..237eb6e 100644 --- a/pkg/debrid/realdebrid.go +++ b/pkg/debrid/realdebrid.go @@ -168,7 +168,6 @@ func (r *RealDebrid) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, er var data structs.RealDebridTorrentInfo err = json.Unmarshal(resp, &data) status := data.Status - fmt.Println("RD STATUS: ", status) name := common.RemoveInvalidChars(data.OriginalFilename) torrent.Name = name // Important because some magnet changes the name torrent.Folder = name @@ -181,7 +180,7 @@ func (r *RealDebrid) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, er torrent.Links = data.Links torrent.Status = status torrent.Debrid = r - downloading_status := []string{"downloading", "magnet_conversion", "queued", "compressing", "uploading"} + downloadingStatus := []string{"downloading", "magnet_conversion", "queued", "compressing", "uploading"} if status == "error" || status == "dead" || status == "magnet_error" { return torrent, fmt.Errorf("torrent: %s has error: %s", torrent.Name, status) } else if status == "waiting_files_selection" { @@ -214,7 +213,7 @@ func (r *RealDebrid) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, er } } break - } else if slices.Contains(downloading_status, status) { + } else if slices.Contains(downloadingStatus, status) { if !r.DownloadUncached { return torrent, fmt.Errorf("torrent: %s not cached", torrent.Name) } diff --git a/pkg/debrid/service.go b/pkg/debrid/service.go index 0fcfce6..de7d0d9 100644 --- a/pkg/debrid/service.go +++ b/pkg/debrid/service.go @@ -11,3 +11,12 @@ func (d *DebridService) Get() Service { } return d.debrids[d.lastUsed] } + +func (d *DebridService) GetByName(name string) Service { + for _, deb := range d.debrids { + if deb.GetName() == name { + return deb + } + } + return nil +} diff --git a/pkg/debrid/torbox.go b/pkg/debrid/torbox.go index e88c387..ba84c7b 100644 --- a/pkg/debrid/torbox.go +++ b/pkg/debrid/torbox.go @@ -45,8 +45,8 @@ func (r *Torbox) IsAvailable(infohashes []string) map[string]bool { } // Divide hashes into groups of 100 - for i := 0; i < len(hashes); i += 200 { - end := i + 200 + for i := 0; i < len(hashes); i += 100 { + end := i + 100 if end > len(hashes) { end = len(hashes) } @@ -165,9 +165,6 @@ func (r *Torbox) GetTorrent(id string) (*Torrent, error) { torrent.Filename = name torrent.OriginalFilename = name files := make([]TorrentFile, 0) - if len(data.Files) == 0 { - return torrent, fmt.Errorf("no files found for torrent: %s", name) - } for _, f := range data.Files { fileName := filepath.Base(f.Name) if (!common.RegexMatch(common.VIDEOMATCH, fileName) && @@ -183,10 +180,13 @@ func (r *Torbox) GetTorrent(id string) (*Torrent, error) { } files = append(files, file) } - if len(files) == 0 { - return torrent, fmt.Errorf("no video files found") + var cleanPath string + if len(files) > 0 { + cleanPath = path.Clean(data.Files[0].Name) + } else { + cleanPath = path.Clean(data.Name) } - cleanPath := path.Clean(data.Files[0].Name) + torrent.OriginalFilename = strings.Split(cleanPath, "/")[0] torrent.Files = files torrent.Debrid = r @@ -229,7 +229,7 @@ func (r *Torbox) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, error) } func (r *Torbox) DeleteTorrent(torrent *Torrent) { - url := fmt.Sprintf("%s/api//torrents/controltorrent/%s", r.Host, torrent.Id) + url := fmt.Sprintf("%s/api/torrents/controltorrent/%s", r.Host, torrent.Id) payload := map[string]string{"torrent_id": torrent.Id, "action": "Delete"} jsonPayload, _ := json.Marshal(payload) req, _ := http.NewRequest(http.MethodDelete, url, bytes.NewBuffer(jsonPayload)) diff --git a/pkg/proxy/proxy.go b/pkg/proxy/proxy.go index 9bf1fb9..86bfa93 100644 --- a/pkg/proxy/proxy.go +++ b/pkg/proxy/proxy.go @@ -75,11 +75,10 @@ type Proxy struct { password string cachedOnly bool debrid debrid.Service - cache *common.Cache logger *log.Logger } -func NewProxy(config common.Config, deb *debrid.DebridService, cache *common.Cache) *Proxy { +func NewProxy(config common.Config, deb *debrid.DebridService) *Proxy { cfg := config.Proxy port := cmp.Or(os.Getenv("PORT"), cfg.Port, "8181") return &Proxy{ @@ -90,7 +89,6 @@ func NewProxy(config common.Config, deb *debrid.DebridService, cache *common.Cac password: cfg.Password, cachedOnly: *cfg.CachedOnly, debrid: deb.Get(), - cache: cache, logger: common.NewLogger("Proxy", os.Stdout), } } diff --git a/pkg/qbit/main.go b/pkg/qbit/main.go index 00ecdb7..928c3a9 100644 --- a/pkg/qbit/main.go +++ b/pkg/qbit/main.go @@ -8,8 +8,8 @@ import ( "goBlack/pkg/qbit/server" ) -func Start(ctx context.Context, config *common.Config, deb *debrid.DebridService, cache *common.Cache) error { - srv := server.NewServer(config, deb, cache) +func Start(ctx context.Context, config *common.Config, deb *debrid.DebridService) error { + srv := server.NewServer(config, deb) if err := srv.Start(ctx); err != nil { return fmt.Errorf("failed to start qbit server: %w", err) } diff --git a/pkg/qbit/server/routes.go b/pkg/qbit/server/routes.go index 2d3334c..23d2d6a 100644 --- a/pkg/qbit/server/routes.go +++ b/pkg/qbit/server/routes.go @@ -45,6 +45,7 @@ func (s *Server) Routes(r chi.Router) http.Handler { r.Get("/episodes/{contentId}", s.handleEpisodes) r.Post("/add", s.handleAddContent) r.Get("/search", s.handleSearch) + r.Get("/cached", s.handleCheckCached) }) return r } diff --git a/pkg/qbit/server/server.go b/pkg/qbit/server/server.go index 2dc0fbb..7185814 100644 --- a/pkg/qbit/server/server.go +++ b/pkg/qbit/server/server.go @@ -22,9 +22,9 @@ type Server struct { debug bool } -func NewServer(config *common.Config, deb *debrid.DebridService, cache *common.Cache) *Server { +func NewServer(config *common.Config, deb *debrid.DebridService) *Server { logger := common.NewLogger("QBit", os.Stdout) - q := shared.NewQBit(config, deb, cache, logger) + q := shared.NewQBit(config, deb, logger) return &Server{ qbit: q, logger: logger, diff --git a/pkg/qbit/server/ui_handlers.go b/pkg/qbit/server/ui_handlers.go index 539e56d..40aad5c 100644 --- a/pkg/qbit/server/ui_handlers.go +++ b/pkg/qbit/server/ui_handlers.go @@ -5,8 +5,10 @@ import ( "encoding/json" "goBlack/common" "goBlack/pkg/arr" + "goBlack/pkg/debrid" "html/template" "net/http" + "strings" ) type AddRequest struct { @@ -112,3 +114,36 @@ func (s *Server) handleAddContent(w http.ResponseWriter, r *http.Request) { } common.JSONResponse(w, importReq, http.StatusOK) } + +func (s *Server) handleCheckCached(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _hashes := r.URL.Query().Get("hash") + if _hashes == "" { + http.Error(w, "No hashes provided", http.StatusBadRequest) + return + } + hashes := strings.Split(_hashes, ",") + if len(hashes) == 0 { + http.Error(w, "No hashes provided", http.StatusBadRequest) + return + } + db := r.URL.Query().Get("debrid") + var deb debrid.Service + if db == "" { + // use the first debrid + deb = s.qbit.Debrid.Get() + } else { + deb = s.qbit.Debrid.GetByName(db) + } + if deb == nil { + http.Error(w, "Invalid debrid", http.StatusBadRequest) + return + } + res := deb.IsAvailable(hashes) + result := make(map[string]bool) + for _, h := range hashes { + _, exists := res[h] + result[h] = exists + } + common.JSONResponse(w, result, http.StatusOK) +} diff --git a/pkg/qbit/shared/qbit.go b/pkg/qbit/shared/qbit.go index 4ae55b2..b5e0c37 100644 --- a/pkg/qbit/shared/qbit.go +++ b/pkg/qbit/shared/qbit.go @@ -16,7 +16,6 @@ type QBit struct { DownloadFolder string `json:"download_folder"` Categories []string `json:"categories"` Debrid *debrid.DebridService - cache *common.Cache Storage *TorrentStorage debug bool logger *log.Logger @@ -24,7 +23,7 @@ type QBit struct { RefreshInterval int } -func NewQBit(config *common.Config, deb *debrid.DebridService, cache *common.Cache, logger *log.Logger) *QBit { +func NewQBit(config *common.Config, deb *debrid.DebridService, logger *log.Logger) *QBit { cfg := config.QBitTorrent port := cmp.Or(cfg.Port, os.Getenv("QBIT_PORT"), "8182") refreshInterval := cmp.Or(cfg.RefreshInterval, 10) @@ -36,7 +35,6 @@ func NewQBit(config *common.Config, deb *debrid.DebridService, cache *common.Cac DownloadFolder: cfg.DownloadFolder, Categories: cfg.Categories, Debrid: deb, - cache: cache, debug: cfg.Debug, Storage: NewTorrentStorage("torrents.json"), logger: logger, diff --git a/pkg/qbit/shared/torrent.go b/pkg/qbit/shared/torrent.go index 7ec61bc..d383bc2 100644 --- a/pkg/qbit/shared/torrent.go +++ b/pkg/qbit/shared/torrent.go @@ -92,7 +92,7 @@ func (q *QBit) ProcessFiles(torrent *Torrent, debridTorrent *debrid.Torrent, arr for debridTorrent.Status != "downloaded" { progress := debridTorrent.Progress q.logger.Printf("%s Download Progress: %.2f%%", debridTorrent.Debrid.GetName(), progress) - time.Sleep(5 * time.Second) + time.Sleep(4 * time.Second) dbT, err := debridTorrent.Debrid.CheckStatus(debridTorrent, isSymlink) if err != nil { q.logger.Printf("Error checking status: %v", err)