Torrent Queuing for Botched torrent (#83)

* Implement a queue for handling failed torrent

* Add checks for getting slots

* Few other cleanups, change some function names
This commit is contained in:
Mukhtar Akere
2025-06-07 17:23:41 +01:00
committed by GitHub
parent 84603b084b
commit 5bf1dab5e6
30 changed files with 556 additions and 239 deletions

View File

@@ -2,13 +2,16 @@ package store
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/sirrobot01/decypharr/internal/request"
"github.com/sirrobot01/decypharr/internal/utils"
"github.com/sirrobot01/decypharr/pkg/arr"
debridTypes "github.com/sirrobot01/decypharr/pkg/debrid/types"
"net/http"
"net/url"
"sync"
"time"
)
@@ -19,23 +22,9 @@ const (
ImportTypeAPI ImportType = "api"
)
func NewImportRequest(debrid string, downloadFolder string, magnet *utils.Magnet, arr *arr.Arr, isSymlink, downloadUncached bool, callBackUrl string, importType ImportType) *ImportRequest {
return &ImportRequest{
Status: "started",
DownloadFolder: downloadFolder,
Debrid: debrid,
Magnet: magnet,
Arr: arr,
IsSymlink: isSymlink,
DownloadUncached: downloadUncached,
CallBackUrl: callBackUrl,
Type: importType,
}
}
type ImportRequest struct {
DownloadFolder string `json:"downloadFolder"`
Debrid string `json:"debrid"`
SelectedDebrid string `json:"debrid"`
Magnet *utils.Magnet `json:"magnet"`
Arr *arr.Arr `json:"arr"`
IsSymlink bool `json:"isSymlink"`
@@ -50,6 +39,20 @@ type ImportRequest struct {
Async bool `json:"async"`
}
func NewImportRequest(debrid string, downloadFolder string, magnet *utils.Magnet, arr *arr.Arr, isSymlink, downloadUncached bool, callBackUrl string, importType ImportType) *ImportRequest {
return &ImportRequest{
Status: "started",
DownloadFolder: downloadFolder,
SelectedDebrid: debrid,
Magnet: magnet,
Arr: arr,
IsSymlink: isSymlink,
DownloadUncached: downloadUncached,
CallBackUrl: callBackUrl,
Type: importType,
}
}
type importResponse struct {
Status string `json:"status"`
CompletedAt time.Time `json:"completedAt"`
@@ -101,3 +104,89 @@ func (i *ImportRequest) markAsCompleted(torrent *Torrent, debridTorrent *debridT
i.CompletedAt = time.Now()
i.sendCallback(torrent, debridTorrent)
}
type ImportQueue struct {
queue map[string]chan *ImportRequest // Map to hold queues for different debrid services
mu sync.RWMutex // Mutex to protect access to the queue map
ctx context.Context
cancel context.CancelFunc
capacity int // Capacity of each channel in the queue
}
func NewImportQueue(ctx context.Context, capacity int) *ImportQueue {
ctx, cancel := context.WithCancel(ctx)
return &ImportQueue{
queue: make(map[string]chan *ImportRequest),
ctx: ctx,
cancel: cancel,
capacity: capacity,
}
}
func (iq *ImportQueue) Push(req *ImportRequest) error {
if req == nil {
return fmt.Errorf("import request cannot be nil")
}
iq.mu.Lock()
defer iq.mu.Unlock()
if _, exists := iq.queue[req.SelectedDebrid]; !exists {
iq.queue[req.SelectedDebrid] = make(chan *ImportRequest, iq.capacity) // Create a new channel for the debrid service
}
select {
case iq.queue[req.SelectedDebrid] <- req:
return nil
case <-iq.ctx.Done():
return fmt.Errorf("retry queue is shutting down")
}
}
func (iq *ImportQueue) TryPop(selectedDebrid string) (*ImportRequest, error) {
iq.mu.RLock()
defer iq.mu.RUnlock()
if ch, exists := iq.queue[selectedDebrid]; exists {
select {
case req := <-ch:
return req, nil
case <-iq.ctx.Done():
return nil, fmt.Errorf("queue is shutting down")
default:
return nil, fmt.Errorf("no import request available for %s", selectedDebrid)
}
}
return nil, fmt.Errorf("no queue exists for %s", selectedDebrid)
}
func (iq *ImportQueue) Size(selectedDebrid string) int {
iq.mu.RLock()
defer iq.mu.RUnlock()
if ch, exists := iq.queue[selectedDebrid]; exists {
return len(ch)
}
return 0
}
func (iq *ImportQueue) Close() {
iq.cancel()
iq.mu.Lock()
defer iq.mu.Unlock()
for _, ch := range iq.queue {
// Drain remaining items before closing
for {
select {
case <-ch:
// Discard remaining items
default:
close(ch)
goto nextChannel
}
}
nextChannel:
}
iq.queue = make(map[string]chan *ImportRequest)
}