package debrid import ( "context" "errors" "fmt" "github.com/sirrobot01/decypharr/internal/config" "github.com/sirrobot01/decypharr/internal/logger" "github.com/sirrobot01/decypharr/internal/utils" "github.com/sirrobot01/decypharr/pkg/arr" "github.com/sirrobot01/decypharr/pkg/debrid/providers/alldebrid" "github.com/sirrobot01/decypharr/pkg/debrid/providers/debrid_link" "github.com/sirrobot01/decypharr/pkg/debrid/providers/realdebrid" "github.com/sirrobot01/decypharr/pkg/debrid/providers/torbox" "github.com/sirrobot01/decypharr/pkg/debrid/store" "github.com/sirrobot01/decypharr/pkg/debrid/types" "sync" ) type Debrid struct { cache *store.Cache // Could be nil if not using WebDAV client types.Client // HTTP client for making requests to the debrid service } func (de *Debrid) Client() types.Client { return de.client } func (de *Debrid) Cache() *store.Cache { return de.cache } type Storage struct { debrids map[string]*Debrid mu sync.RWMutex lastUsed string } func NewStorage() *Storage { cfg := config.Get() _logger := logger.Default() debrids := make(map[string]*Debrid) for _, dc := range cfg.Debrids { client, err := createDebridClient(dc) if err != nil { _logger.Error().Err(err).Str("Debrid", dc.Name).Msg("failed to connect to debrid client") continue } var cache *store.Cache _log := client.Logger() if dc.UseWebDav { cache = store.NewDebridCache(dc, client) _log.Info().Msg("Debrid Service started with WebDAV") } else { _log.Info().Msg("Debrid Service started") } debrids[dc.Name] = &Debrid{ cache: cache, client: client, } } d := &Storage{ debrids: debrids, lastUsed: "", } return d } func (d *Storage) Debrid(name string) *Debrid { d.mu.RLock() defer d.mu.RUnlock() if debrid, exists := d.debrids[name]; exists { return debrid } return nil } func (d *Storage) Debrids() map[string]*Debrid { d.mu.RLock() defer d.mu.RUnlock() debridsCopy := make(map[string]*Debrid) for name, debrid := range d.debrids { if debrid != nil { debridsCopy[name] = debrid } } return debridsCopy } func (d *Storage) Client(name string) types.Client { d.mu.RLock() defer d.mu.RUnlock() if client, exists := d.debrids[name]; exists { return client.client } return nil } func (d *Storage) Reset() { d.mu.Lock() d.debrids = make(map[string]*Debrid) d.mu.Unlock() d.lastUsed = "" } func (d *Storage) Clients() map[string]types.Client { d.mu.RLock() defer d.mu.RUnlock() clientsCopy := make(map[string]types.Client) for name, debrid := range d.debrids { if debrid != nil && debrid.client != nil { clientsCopy[name] = debrid.client } } return clientsCopy } func (d *Storage) Caches() map[string]*store.Cache { d.mu.RLock() defer d.mu.RUnlock() cachesCopy := make(map[string]*store.Cache) for name, debrid := range d.debrids { if debrid != nil && debrid.cache != nil { cachesCopy[name] = debrid.cache } } return cachesCopy } func (d *Storage) FilterClients(filter func(types.Client) bool) map[string]types.Client { d.mu.Lock() defer d.mu.Unlock() filteredClients := make(map[string]types.Client) for name, client := range d.debrids { if client != nil && filter(client.client) { filteredClients[name] = client.client } } return filteredClients } func createDebridClient(dc config.Debrid) (types.Client, error) { switch dc.Name { case "realdebrid": return realdebrid.New(dc) case "torbox": return torbox.New(dc) case "debridlink": return debrid_link.New(dc) case "alldebrid": return alldebrid.New(dc) default: return realdebrid.New(dc) } } func Process(ctx context.Context, store *Storage, selectedDebrid string, magnet *utils.Magnet, a *arr.Arr, action string, overrideDownloadUncached bool) (*types.Torrent, error) { debridTorrent := &types.Torrent{ InfoHash: magnet.InfoHash, Magnet: magnet, Name: magnet.Name, Arr: a, Size: magnet.Size, Files: make(map[string]types.File), } clients := store.FilterClients(func(c types.Client) bool { if selectedDebrid != "" && c.Name() != selectedDebrid { return false } return true }) if len(clients) == 0 { return nil, fmt.Errorf("no debrid clients available") } errs := make([]error, 0, len(clients)) // Override first, arr second, debrid third if overrideDownloadUncached { debridTorrent.DownloadUncached = true } else if a.DownloadUncached != nil { // Arr cached is set debridTorrent.DownloadUncached = *a.DownloadUncached } else { debridTorrent.DownloadUncached = false } for index, db := range clients { _logger := db.Logger() _logger.Info(). Str("Debrid", db.Name()). Str("Arr", a.Name). Str("Hash", debridTorrent.InfoHash). Str("Name", debridTorrent.Name). Str("Action", action). Msg("Processing torrent") if !overrideDownloadUncached && a.DownloadUncached == nil { debridTorrent.DownloadUncached = db.GetDownloadUncached() } dbt, err := db.SubmitMagnet(debridTorrent) if err != nil || dbt == nil || dbt.Id == "" { errs = append(errs, err) continue } dbt.Arr = a _logger.Info().Str("id", dbt.Id).Msgf("Torrent: %s submitted to %s", dbt.Name, db.Name()) store.lastUsed = index torrent, err := db.CheckStatus(dbt) if err != nil && torrent != nil && torrent.Id != "" { // Delete the torrent if it was not downloaded go func(id string) { _ = db.DeleteTorrent(id) }(torrent.Id) } if err != nil { errs = append(errs, err) continue } if torrent == nil { errs = append(errs, fmt.Errorf("torrent %s returned nil after checking status", dbt.Name)) continue } return torrent, nil } if len(errs) == 0 { return nil, fmt.Errorf("failed to process torrent: no clients available") } joinedErrors := errors.Join(errs...) return nil, fmt.Errorf("failed to process torrent: %w", joinedErrors) }