fix autodownloader download now for queued items

This commit is contained in:
5rahim
2026-04-08 10:04:06 +02:00
parent 52449916e0
commit 94fcf2557b
4 changed files with 209 additions and 4 deletions

View File

@@ -2,14 +2,19 @@ package handlers
import (
"errors"
"fmt"
"os"
"path/filepath"
"seanime/internal/api/anilist"
"seanime/internal/database/db_bridge"
"seanime/internal/database/models"
hibiketorrent "seanime/internal/extension/hibike/torrent"
"seanime/internal/library/autodownloader"
"seanime/internal/torrent_clients/torrent_client"
torrentrepo "seanime/internal/torrents/torrent"
"seanime/internal/util"
"github.com/goccy/go-json"
"github.com/labstack/echo/v4"
)
@@ -314,10 +319,30 @@ func (h *Handler) HandleTorrentClientAddMagnetFromRule(c echo.Context) error {
return h.RespondWithError(c, err)
}
if b.MagnetUrl == "" || b.RuleId == 0 {
if b.RuleId == 0 || (b.MagnetUrl == "" && b.QueuedItemId == 0) {
return h.RespondWithError(c, errors.New("missing parameters"))
}
magnetURL := b.MagnetUrl
if magnetURL == "" {
item, err := h.App.Database.GetAutoDownloaderItem(b.QueuedItemId)
if err != nil {
return h.RespondWithError(c, err)
}
magnetURL, err = resolveAutoDownloaderItemMagnet(item, h.App.TorrentRepository)
if err != nil {
return h.RespondWithError(c, err)
}
if item.Magnet != magnetURL {
item.Magnet = magnetURL
if err := h.App.Database.UpdateAutoDownloaderItem(item.ID, item); err != nil {
h.App.Logger.Warn().Err(err).Uint("queuedItemId", item.ID).Msg("torrent client: Failed to cache resolved queued magnet")
}
}
}
// Get rule from database
rule, err := db_bridge.GetAutoDownloaderRule(h.App.Database, b.RuleId)
if err != nil {
@@ -331,7 +356,7 @@ func (h *Handler) HandleTorrentClientAddMagnetFromRule(c echo.Context) error {
}
// try to add torrents to client, on error return error
err = h.App.TorrentClientRepository.AddMagnets([]string{b.MagnetUrl}, rule.Destination)
err = h.App.TorrentClientRepository.AddMagnets([]string{magnetURL}, rule.Destination)
if err != nil {
return h.RespondWithError(c, err)
}
@@ -344,3 +369,49 @@ func (h *Handler) HandleTorrentClientAddMagnetFromRule(c echo.Context) error {
return h.RespondWithData(c, true)
}
func resolveAutoDownloaderItemMagnet(item *models.AutoDownloaderItem, torrentRepository *torrentrepo.Repository) (string, error) {
if item == nil {
return "", errors.New("queued item not found")
}
if item.Magnet != "" {
return item.Magnet, nil
}
fallbackHash := item.Hash
var resolveErr error
if len(item.TorrentData) > 0 {
var storedTorrent autodownloader.NormalizedTorrent
if err := json.Unmarshal(item.TorrentData, &storedTorrent); err != nil {
resolveErr = err
} else if storedTorrent.AnimeTorrent != nil {
if fallbackHash == "" {
fallbackHash = storedTorrent.AnimeTorrent.InfoHash
}
if storedTorrent.AnimeTorrent.Provider == "" && storedTorrent.ExtensionID != "" {
storedTorrent.AnimeTorrent.Provider = storedTorrent.ExtensionID
}
if torrentRepository != nil {
magnet, err := torrentRepository.ResolveMagnetLink(storedTorrent.AnimeTorrent)
if err == nil && magnet != "" {
return magnet, nil
}
resolveErr = err
}
}
}
if fallbackHash != "" {
return fmt.Sprintf("magnet:?xt=urn:btih:%s", fallbackHash), nil
}
if resolveErr != nil {
return "", resolveErr
}
return "", errors.New("magnet link not found")
}

View File

@@ -0,0 +1,129 @@
package handlers
import (
"errors"
"testing"
"github.com/goccy/go-json"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"seanime/internal/api/metadata_provider"
"seanime/internal/database/models"
"seanime/internal/extension"
hibiketorrent "seanime/internal/extension/hibike/torrent"
"seanime/internal/library/autodownloader"
torrentrepo "seanime/internal/torrents/torrent"
"seanime/internal/util"
)
type ruleMagnetTestProvider struct {
magnet string
err error
calls int
}
func (p *ruleMagnetTestProvider) Search(hibiketorrent.AnimeSearchOptions) ([]*hibiketorrent.AnimeTorrent, error) {
return nil, nil
}
func (p *ruleMagnetTestProvider) SmartSearch(hibiketorrent.AnimeSmartSearchOptions) ([]*hibiketorrent.AnimeTorrent, error) {
return nil, nil
}
func (p *ruleMagnetTestProvider) GetTorrentInfoHash(torrent *hibiketorrent.AnimeTorrent) (string, error) {
if torrent == nil {
return "", nil
}
return torrent.InfoHash, nil
}
func (p *ruleMagnetTestProvider) GetTorrentMagnetLink(*hibiketorrent.AnimeTorrent) (string, error) {
p.calls++
if p.err != nil {
return "", p.err
}
return p.magnet, nil
}
func (p *ruleMagnetTestProvider) GetLatest() ([]*hibiketorrent.AnimeTorrent, error) {
return nil, nil
}
func (p *ruleMagnetTestProvider) GetSettings() hibiketorrent.AnimeProviderSettings {
return hibiketorrent.AnimeProviderSettings{Type: hibiketorrent.AnimeProviderTypeMain}
}
func TestResolveAutoDownloaderItemMagnetUsesStoredTorrentExtension(t *testing.T) {
provider := &ruleMagnetTestProvider{magnet: "magnet:?xt=urn:btih:resolved-from-provider"}
repo := newTorrentRepositoryForRuleMagnetTests(map[string]*ruleMagnetTestProvider{"fake": provider})
torrentData, err := json.Marshal(&autodownloader.NormalizedTorrent{
AnimeTorrent: &hibiketorrent.AnimeTorrent{
Name: "Example torrent",
InfoHash: "hash-from-torrent",
},
ExtensionID: "fake",
})
require.NoError(t, err)
item := &models.AutoDownloaderItem{
Hash: "hash-from-item",
TorrentData: torrentData,
}
magnet, err := resolveAutoDownloaderItemMagnet(item, repo)
require.NoError(t, err)
assert.Equal(t, "magnet:?xt=urn:btih:resolved-from-provider", magnet)
assert.Equal(t, 1, provider.calls)
}
func TestResolveAutoDownloaderItemMagnetFallsBackToHash(t *testing.T) {
provider := &ruleMagnetTestProvider{err: errors.New("provider failed")}
repo := newTorrentRepositoryForRuleMagnetTests(map[string]*ruleMagnetTestProvider{"fake": provider})
torrentData, err := json.Marshal(&autodownloader.NormalizedTorrent{
AnimeTorrent: &hibiketorrent.AnimeTorrent{
Name: "Example torrent",
InfoHash: "hash-from-torrent",
},
ExtensionID: "fake",
})
require.NoError(t, err)
item := &models.AutoDownloaderItem{
Hash: "hash-from-item",
TorrentData: torrentData,
}
magnet, err := resolveAutoDownloaderItemMagnet(item, repo)
require.NoError(t, err)
assert.Equal(t, "magnet:?xt=urn:btih:hash-from-item", magnet)
assert.Equal(t, 1, provider.calls)
}
func newTorrentRepositoryForRuleMagnetTests(providers map[string]*ruleMagnetTestProvider) *torrentrepo.Repository {
logger := zerolog.Nop()
bank := extension.NewUnifiedBank()
for id, provider := range providers {
bank.Set(id, extension.NewAnimeTorrentProviderExtension(&extension.Extension{
ID: id,
Name: id,
Version: "1.0.0",
ManifestURI: "builtin",
Language: extension.LanguageGo,
Type: extension.TypeAnimeTorrentProvider,
}, provider))
}
var metadata metadata_provider.Provider
repo := torrentrepo.NewRepository(&torrentrepo.NewRepositoryOptions{
Logger: &logger,
MetadataProviderRef: util.NewRef[metadata_provider.Provider](metadata),
ExtensionBankRef: util.NewRef(bank),
})
repo.SetSettings(&torrentrepo.RepositorySettings{DefaultAnimeProvider: "fake"})
return repo
}

View File

@@ -665,6 +665,7 @@ func (ad *AutoDownloader) handleDelayedItem(
storedItem.Link = bestCandidate.Torrent.Link
storedItem.Hash = bestCandidate.Torrent.InfoHash
storedItem.Magnet = bestCandidate.Torrent.MagnetLink
storedItem.TorrentName = bestCandidate.Torrent.Name
storedItem.Score = bestCandidate.Score
// Do NOT reset DelayUntil, keep the original timer
@@ -899,6 +900,7 @@ func (ad *AutoDownloader) queueTorrentForDelay(isSimulation bool, rule *anime.Au
Episode: episode,
Link: candidate.Torrent.Link,
Hash: candidate.Torrent.InfoHash,
Magnet: candidate.Torrent.MagnetLink,
TorrentName: candidate.Torrent.Name,
Downloaded: false,
IsDelayed: true,

View File

@@ -1250,7 +1250,7 @@ func TestDelayIntegration(t *testing.T) {
{
name: "Queue item for delay",
torrents: []*hibiketorrent.AnimeTorrent{
{Name: "[SubsPlease] Sousou no Frieren - 01 (1080p).mkv", InfoHash: "hash1", Seeders: 100},
{Name: "[SubsPlease] Sousou no Frieren - 01 (1080p).mkv", InfoHash: "hash1", MagnetLink: "magnet:?xt=urn:btih:hash1", Seeders: 100},
},
profile: &anime.AutoDownloaderProfile{
Conditions: []anime.AutoDownloaderCondition{{Term: "1080p", Action: anime.AutoDownloaderProfileRuleFormatActionScore, Score: 10}},
@@ -1263,6 +1263,7 @@ func TestDelayIntegration(t *testing.T) {
assert.True(t, items[0].IsDelayed) // MUST be true
assert.False(t, items[0].Downloaded) // will always be false
assert.Equal(t, "hash1", items[0].Hash)
assert.Equal(t, "magnet:?xt=urn:btih:hash1", items[0].Magnet)
},
},
{
@@ -1312,7 +1313,7 @@ func TestDelayIntegration(t *testing.T) {
{
name: "Upgrade delayed item",
torrents: []*hibiketorrent.AnimeTorrent{
{Name: "[BetterGroup] Sousou no Frieren - 01 (1080p).mkv", InfoHash: "hash_better", Seeders: 100}, // Score 20
{Name: "[BetterGroup] Sousou no Frieren - 01 (1080p).mkv", InfoHash: "hash_better", MagnetLink: "magnet:?xt=urn:btih:hash_better", Seeders: 100}, // Score 20
},
existingItems: []*models.AutoDownloaderItem{
{
@@ -1320,6 +1321,7 @@ func TestDelayIntegration(t *testing.T) {
MediaID: mediaId,
Episode: 1,
Hash: "hash_bad",
Magnet: "magnet:?xt=urn:btih:hash_bad",
Score: 10,
IsDelayed: true,
DelayUntil: time.Now().Add(5 * time.Minute), // Not expired
@@ -1339,6 +1341,7 @@ func TestDelayIntegration(t *testing.T) {
require.Len(t, items, 1)
assert.True(t, items[0].IsDelayed)
assert.Equal(t, "hash_better", items[0].Hash) // Updated hash
assert.Equal(t, "magnet:?xt=urn:btih:hash_better", items[0].Magnet)
assert.Equal(t, 20, items[0].Score)
},
},