node build fixed
This commit is contained in:
@@ -0,0 +1 @@
|
||||
package torrent_client
|
||||
@@ -0,0 +1,420 @@
|
||||
package torrent_client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/hekmon/transmissionrpc/v3"
|
||||
"github.com/rs/zerolog"
|
||||
"seanime/internal/api/metadata"
|
||||
"seanime/internal/events"
|
||||
"seanime/internal/torrent_clients/qbittorrent"
|
||||
"seanime/internal/torrent_clients/qbittorrent/model"
|
||||
"seanime/internal/torrent_clients/transmission"
|
||||
"seanime/internal/torrents/torrent"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
QbittorrentClient = "qbittorrent"
|
||||
TransmissionClient = "transmission"
|
||||
NoneClient = "none"
|
||||
)
|
||||
|
||||
type (
|
||||
Repository struct {
|
||||
logger *zerolog.Logger
|
||||
qBittorrentClient *qbittorrent.Client
|
||||
transmission *transmission.Transmission
|
||||
torrentRepository *torrent.Repository
|
||||
provider string
|
||||
metadataProvider metadata.Provider
|
||||
activeTorrentCountCtxCancel context.CancelFunc
|
||||
activeTorrentCount *ActiveCount
|
||||
}
|
||||
|
||||
NewRepositoryOptions struct {
|
||||
Logger *zerolog.Logger
|
||||
QbittorrentClient *qbittorrent.Client
|
||||
Transmission *transmission.Transmission
|
||||
TorrentRepository *torrent.Repository
|
||||
Provider string
|
||||
MetadataProvider metadata.Provider
|
||||
}
|
||||
|
||||
ActiveCount struct {
|
||||
Downloading int `json:"downloading"`
|
||||
Seeding int `json:"seeding"`
|
||||
Paused int `json:"paused"`
|
||||
}
|
||||
)
|
||||
|
||||
func NewRepository(opts *NewRepositoryOptions) *Repository {
|
||||
if opts.Provider == "" {
|
||||
opts.Provider = QbittorrentClient
|
||||
}
|
||||
return &Repository{
|
||||
logger: opts.Logger,
|
||||
qBittorrentClient: opts.QbittorrentClient,
|
||||
transmission: opts.Transmission,
|
||||
torrentRepository: opts.TorrentRepository,
|
||||
provider: opts.Provider,
|
||||
metadataProvider: opts.MetadataProvider,
|
||||
activeTorrentCount: &ActiveCount{},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Repository) Shutdown() {
|
||||
if r.activeTorrentCountCtxCancel != nil {
|
||||
r.activeTorrentCountCtxCancel()
|
||||
r.activeTorrentCountCtxCancel = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Repository) InitActiveTorrentCount(enabled bool, wsEventManager events.WSEventManagerInterface) {
|
||||
if r.activeTorrentCountCtxCancel != nil {
|
||||
r.activeTorrentCountCtxCancel()
|
||||
}
|
||||
|
||||
if !enabled {
|
||||
return
|
||||
}
|
||||
|
||||
var ctx context.Context
|
||||
ctx, r.activeTorrentCountCtxCancel = context.WithCancel(context.Background())
|
||||
go func(ctx context.Context) {
|
||||
ticker := time.NewTicker(time.Second * 5)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
r.GetActiveCount(r.activeTorrentCount)
|
||||
wsEventManager.SendEvent(events.ActiveTorrentCountUpdated, r.activeTorrentCount)
|
||||
}
|
||||
}
|
||||
}(ctx)
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func (r *Repository) GetProvider() string {
|
||||
return r.provider
|
||||
}
|
||||
|
||||
func (r *Repository) Start() bool {
|
||||
switch r.provider {
|
||||
case QbittorrentClient:
|
||||
return r.qBittorrentClient.CheckStart()
|
||||
case TransmissionClient:
|
||||
return r.transmission.CheckStart()
|
||||
case NoneClient:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
func (r *Repository) TorrentExists(hash string) bool {
|
||||
switch r.provider {
|
||||
case QbittorrentClient:
|
||||
p, err := r.qBittorrentClient.Torrent.GetProperties(hash)
|
||||
return err == nil && p != nil
|
||||
case TransmissionClient:
|
||||
torrents, err := r.transmission.Client.TorrentGetAllForHashes(context.Background(), []string{hash})
|
||||
return err == nil && len(torrents) > 0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// GetList will return all torrents from the torrent client.
|
||||
func (r *Repository) GetList() ([]*Torrent, error) {
|
||||
switch r.provider {
|
||||
case QbittorrentClient:
|
||||
torrents, err := r.qBittorrentClient.Torrent.GetList(&qbittorrent_model.GetTorrentListOptions{Filter: "all"})
|
||||
if err != nil {
|
||||
r.logger.Err(err).Msg("torrent client: Error while getting torrent list (qBittorrent)")
|
||||
return nil, err
|
||||
}
|
||||
return r.FromQbitTorrents(torrents), nil
|
||||
case TransmissionClient:
|
||||
torrents, err := r.transmission.Client.TorrentGetAll(context.Background())
|
||||
if err != nil {
|
||||
r.logger.Err(err).Msg("torrent client: Error while getting torrent list (Transmission)")
|
||||
return nil, err
|
||||
}
|
||||
return r.FromTransmissionTorrents(torrents), nil
|
||||
default:
|
||||
return nil, errors.New("torrent client: No torrent client provider found")
|
||||
}
|
||||
}
|
||||
|
||||
// GetActiveCount will return the count of active torrents (downloading, seeding, paused).
|
||||
func (r *Repository) GetActiveCount(ret *ActiveCount) {
|
||||
ret.Seeding = 0
|
||||
ret.Downloading = 0
|
||||
ret.Paused = 0
|
||||
switch r.provider {
|
||||
case QbittorrentClient:
|
||||
torrents, err := r.qBittorrentClient.Torrent.GetList(&qbittorrent_model.GetTorrentListOptions{Filter: "downloading"})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
torrents2, err := r.qBittorrentClient.Torrent.GetList(&qbittorrent_model.GetTorrentListOptions{Filter: "seeding"})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
torrents = append(torrents, torrents2...)
|
||||
for _, t := range torrents {
|
||||
switch fromQbitTorrentStatus(t.State) {
|
||||
case TorrentStatusDownloading:
|
||||
ret.Downloading++
|
||||
case TorrentStatusSeeding:
|
||||
ret.Seeding++
|
||||
case TorrentStatusPaused:
|
||||
ret.Paused++
|
||||
}
|
||||
}
|
||||
case TransmissionClient:
|
||||
torrents, err := r.transmission.Client.TorrentGet(context.Background(), []string{"id", "status", "isFinished"}, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, t := range torrents {
|
||||
if t.Status == nil || t.IsFinished == nil {
|
||||
continue
|
||||
}
|
||||
switch fromTransmissionTorrentStatus(*t.Status, *t.IsFinished) {
|
||||
case TorrentStatusDownloading:
|
||||
ret.Downloading++
|
||||
case TorrentStatusSeeding:
|
||||
ret.Seeding++
|
||||
case TorrentStatusPaused:
|
||||
ret.Paused++
|
||||
}
|
||||
}
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// GetActiveTorrents will return all torrents that are currently downloading, paused or seeding.
|
||||
func (r *Repository) GetActiveTorrents() ([]*Torrent, error) {
|
||||
torrents, err := r.GetList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var active []*Torrent
|
||||
for _, t := range torrents {
|
||||
if t.Status == TorrentStatusDownloading || t.Status == TorrentStatusSeeding || t.Status == TorrentStatusPaused {
|
||||
active = append(active, t)
|
||||
}
|
||||
}
|
||||
return active, nil
|
||||
}
|
||||
|
||||
func (r *Repository) AddMagnets(magnets []string, dest string) error {
|
||||
r.logger.Trace().Any("magnets", magnets).Msg("torrent client: Adding magnets")
|
||||
|
||||
if len(magnets) == 0 {
|
||||
r.logger.Debug().Msg("torrent client: No magnets to add")
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
switch r.provider {
|
||||
case QbittorrentClient:
|
||||
err = r.qBittorrentClient.Torrent.AddURLs(magnets, &qbittorrent_model.AddTorrentsOptions{
|
||||
Savepath: dest,
|
||||
Tags: r.qBittorrentClient.Tags,
|
||||
})
|
||||
case TransmissionClient:
|
||||
for _, magnet := range magnets {
|
||||
_, err = r.transmission.Client.TorrentAdd(context.Background(), transmissionrpc.TorrentAddPayload{
|
||||
Filename: &magnet,
|
||||
DownloadDir: &dest,
|
||||
})
|
||||
if err != nil {
|
||||
r.logger.Err(err).Msg("torrent client: Error while adding magnets (Transmission)")
|
||||
break
|
||||
}
|
||||
}
|
||||
case NoneClient:
|
||||
return errors.New("torrent client: No torrent client selected")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
r.logger.Err(err).Msg("torrent client: Error while adding magnets")
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Debug().Msg("torrent client: Added torrents")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) RemoveTorrents(hashes []string) error {
|
||||
r.logger.Trace().Msg("torrent client: Removing torrents")
|
||||
|
||||
var err error
|
||||
switch r.provider {
|
||||
case QbittorrentClient:
|
||||
err = r.qBittorrentClient.Torrent.DeleteTorrents(hashes, true)
|
||||
case TransmissionClient:
|
||||
torrents, err := r.transmission.Client.TorrentGetAllForHashes(context.Background(), hashes)
|
||||
if err != nil {
|
||||
r.logger.Err(err).Msg("torrent client: Error while fetching torrents (Transmission)")
|
||||
return err
|
||||
}
|
||||
ids := make([]int64, len(torrents))
|
||||
for i, t := range torrents {
|
||||
ids[i] = *t.ID
|
||||
}
|
||||
err = r.transmission.Client.TorrentRemove(context.Background(), transmissionrpc.TorrentRemovePayload{
|
||||
IDs: ids,
|
||||
DeleteLocalData: true,
|
||||
})
|
||||
if err != nil {
|
||||
r.logger.Err(err).Msg("torrent client: Error while removing torrents (Transmission)")
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
r.logger.Err(err).Msg("torrent client: Error while removing torrents")
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Debug().Any("hashes", hashes).Msg("torrent client: Removed torrents")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) PauseTorrents(hashes []string) error {
|
||||
r.logger.Trace().Msg("torrent client: Pausing torrents")
|
||||
|
||||
var err error
|
||||
switch r.provider {
|
||||
case QbittorrentClient:
|
||||
err = r.qBittorrentClient.Torrent.StopTorrents(hashes)
|
||||
case TransmissionClient:
|
||||
err = r.transmission.Client.TorrentStopHashes(context.Background(), hashes)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
r.logger.Err(err).Msg("torrent client: Error while pausing torrents")
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Debug().Any("hashes", hashes).Msg("torrent client: Paused torrents")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) ResumeTorrents(hashes []string) error {
|
||||
r.logger.Trace().Msg("torrent client: Resuming torrents")
|
||||
|
||||
var err error
|
||||
switch r.provider {
|
||||
case QbittorrentClient:
|
||||
err = r.qBittorrentClient.Torrent.ResumeTorrents(hashes)
|
||||
case TransmissionClient:
|
||||
err = r.transmission.Client.TorrentStartHashes(context.Background(), hashes)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
r.logger.Err(err).Msg("torrent client: Error while resuming torrents")
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Debug().Any("hashes", hashes).Msg("torrent client: Resumed torrents")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) DeselectFiles(hash string, indices []int) error {
|
||||
|
||||
var err error
|
||||
switch r.provider {
|
||||
case QbittorrentClient:
|
||||
strIndices := make([]string, len(indices), len(indices))
|
||||
for i, v := range indices {
|
||||
strIndices[i] = strconv.Itoa(v)
|
||||
}
|
||||
err = r.qBittorrentClient.Torrent.SetFilePriorities(hash, strIndices, 0)
|
||||
case TransmissionClient:
|
||||
torrents, err := r.transmission.Client.TorrentGetAllForHashes(context.Background(), []string{hash})
|
||||
if err != nil || torrents[0].ID == nil {
|
||||
r.logger.Err(err).Msg("torrent client: Error while deselecting files (Transmission)")
|
||||
return err
|
||||
}
|
||||
id := *torrents[0].ID
|
||||
ind := make([]int64, len(indices), len(indices))
|
||||
for i, v := range indices {
|
||||
ind[i] = int64(v)
|
||||
}
|
||||
err = r.transmission.Client.TorrentSet(context.Background(), transmissionrpc.TorrentSetPayload{
|
||||
FilesUnwanted: ind,
|
||||
IDs: []int64{id},
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
r.logger.Err(err).Msg("torrent client: Error while deselecting files")
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Debug().Str("hash", hash).Any("indices", indices).Msg("torrent client: Deselected torrent files")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFiles blocks until the files are retrieved, or until timeout.
|
||||
func (r *Repository) GetFiles(hash string) (filenames []string, err error) {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
filenames = make([]string, 0)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
r.logger.Debug().Str("hash", hash).Msg("torrent client: Getting torrent files")
|
||||
defer close(done)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = errors.New("torrent client: Unable to retrieve torrent files (timeout)")
|
||||
return
|
||||
case <-ticker.C:
|
||||
switch r.provider {
|
||||
case QbittorrentClient:
|
||||
qbitFiles, err := r.qBittorrentClient.Torrent.GetContents(hash)
|
||||
if err == nil && qbitFiles != nil && len(qbitFiles) > 0 {
|
||||
r.logger.Debug().Str("hash", hash).Int("count", len(qbitFiles)).Msg("torrent client: Retrieved torrent files")
|
||||
for _, f := range qbitFiles {
|
||||
filenames = append(filenames, f.Name)
|
||||
}
|
||||
return
|
||||
}
|
||||
case TransmissionClient:
|
||||
torrents, err := r.transmission.Client.TorrentGetAllForHashes(context.Background(), []string{hash})
|
||||
if err == nil && len(torrents) > 0 && torrents[0].Files != nil && len(torrents[0].Files) > 0 {
|
||||
transmissionFiles := torrents[0].Files
|
||||
r.logger.Debug().Str("hash", hash).Int("count", len(transmissionFiles)).Msg("torrent client: Retrieved torrent files")
|
||||
for _, f := range transmissionFiles {
|
||||
filenames = append(filenames, f.Name)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
<-done // wait for the files to be retrieved
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package torrent_client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"seanime/internal/api/anilist"
|
||||
"seanime/internal/platforms/platform"
|
||||
"seanime/internal/torrents/analyzer"
|
||||
"time"
|
||||
|
||||
hibiketorrent "seanime/internal/extension/hibike/torrent"
|
||||
)
|
||||
|
||||
type (
|
||||
SmartSelectParams struct {
|
||||
Torrent *hibiketorrent.AnimeTorrent
|
||||
EpisodeNumbers []int
|
||||
Media *anilist.CompleteAnime
|
||||
Destination string
|
||||
ShouldAddTorrent bool
|
||||
Platform platform.Platform
|
||||
}
|
||||
)
|
||||
|
||||
// SmartSelect will automatically the provided episode files from the torrent.
|
||||
// If the torrent has not been added yet, set SmartSelect.ShouldAddTorrent to true.
|
||||
// The torrent will NOT be removed if the selection fails.
|
||||
func (r *Repository) SmartSelect(p *SmartSelectParams) error {
|
||||
if p.Media == nil || p.Platform == nil || r.torrentRepository == nil {
|
||||
r.logger.Error().Msg("torrent client: media or platform is nil (smart select)")
|
||||
return errors.New("media or anilist client wrapper is nil")
|
||||
}
|
||||
|
||||
providerExtension, ok := r.torrentRepository.GetAnimeProviderExtension(p.Torrent.Provider)
|
||||
if !ok {
|
||||
r.logger.Error().Str("provider", p.Torrent.Provider).Msg("torrent client: provider extension not found (smart select)")
|
||||
return errors.New("provider extension not found")
|
||||
}
|
||||
|
||||
if p.Media.IsMovieOrSingleEpisode() {
|
||||
return errors.New("smart select is not supported for movies or single-episode series")
|
||||
}
|
||||
|
||||
if len(p.EpisodeNumbers) == 0 {
|
||||
r.logger.Error().Msg("torrent client: no episode numbers provided (smart select)")
|
||||
return errors.New("no episode numbers provided")
|
||||
}
|
||||
|
||||
if p.ShouldAddTorrent {
|
||||
r.logger.Info().Msg("torrent client: adding torrent (smart select)")
|
||||
// Get magnet
|
||||
magnet, err := providerExtension.GetProvider().GetTorrentMagnetLink(p.Torrent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Add the torrent
|
||||
err = r.AddMagnets([]string{magnet}, p.Destination)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
filepaths, err := r.GetFiles(p.Torrent.InfoHash)
|
||||
if err != nil {
|
||||
r.logger.Err(err).Msg("torrent client: error getting files (smart select)")
|
||||
_ = r.RemoveTorrents([]string{p.Torrent.InfoHash})
|
||||
return fmt.Errorf("error getting files, torrent still added: %w", err)
|
||||
}
|
||||
|
||||
// Pause the torrent
|
||||
err = r.PauseTorrents([]string{p.Torrent.InfoHash})
|
||||
if err != nil {
|
||||
r.logger.Err(err).Msg("torrent client: error while pausing torrent (smart select)")
|
||||
_ = r.RemoveTorrents([]string{p.Torrent.InfoHash})
|
||||
return fmt.Errorf("error while selecting files: %w", err)
|
||||
}
|
||||
|
||||
// AnalyzeTorrentFiles the torrent files
|
||||
analyzer := torrent_analyzer.NewAnalyzer(&torrent_analyzer.NewAnalyzerOptions{
|
||||
Logger: r.logger,
|
||||
Filepaths: filepaths,
|
||||
Media: p.Media,
|
||||
Platform: p.Platform,
|
||||
MetadataProvider: r.metadataProvider,
|
||||
})
|
||||
|
||||
r.logger.Debug().Msg("torrent client: analyzing torrent files (smart select)")
|
||||
|
||||
analysis, err := analyzer.AnalyzeTorrentFiles()
|
||||
if err != nil {
|
||||
r.logger.Err(err).Msg("torrent client: error while analyzing torrent files (smart select)")
|
||||
_ = r.RemoveTorrents([]string{p.Torrent.InfoHash})
|
||||
return fmt.Errorf("error while analyzing torrent files: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Debug().Msg("torrent client: finished analyzing torrent files (smart select)")
|
||||
|
||||
mainFiles := analysis.GetCorrespondingMainFiles()
|
||||
|
||||
// find episode number duplicates
|
||||
dup := make(map[int]int) // map[episodeNumber]count
|
||||
for _, f := range mainFiles {
|
||||
if _, ok := dup[f.GetLocalFile().GetEpisodeNumber()]; ok {
|
||||
dup[f.GetLocalFile().GetEpisodeNumber()]++
|
||||
} else {
|
||||
dup[f.GetLocalFile().GetEpisodeNumber()] = 1
|
||||
}
|
||||
}
|
||||
dupCount := 0
|
||||
for _, count := range dup {
|
||||
if count > 1 {
|
||||
dupCount++
|
||||
}
|
||||
}
|
||||
if dupCount > 2 {
|
||||
_ = r.RemoveTorrents([]string{p.Torrent.InfoHash})
|
||||
return errors.New("failed to select files, can't tell seasons apart")
|
||||
}
|
||||
|
||||
selectedFiles := make(map[int]*torrent_analyzer.File)
|
||||
selectedCount := 0
|
||||
for idx, f := range mainFiles {
|
||||
for _, ep := range p.EpisodeNumbers {
|
||||
if f.GetLocalFile().GetEpisodeNumber() == ep {
|
||||
selectedCount++
|
||||
selectedFiles[idx] = f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if selectedCount == 0 || selectedCount < len(p.EpisodeNumbers) {
|
||||
_ = r.RemoveTorrents([]string{p.Torrent.InfoHash})
|
||||
return errors.New("failed to select files, could not find the right season files")
|
||||
}
|
||||
|
||||
indicesToRemove := analysis.GetUnselectedIndices(selectedFiles)
|
||||
|
||||
if len(indicesToRemove) > 0 {
|
||||
// Deselect files
|
||||
err = r.DeselectFiles(p.Torrent.InfoHash, indicesToRemove)
|
||||
if err != nil {
|
||||
r.logger.Err(err).Msg("torrent client: error while deselecting files (smart select)")
|
||||
_ = r.RemoveTorrents([]string{p.Torrent.InfoHash})
|
||||
return fmt.Errorf("error while deselecting files: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Resume the torrent
|
||||
_ = r.ResumeTorrents([]string{p.Torrent.InfoHash})
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package torrent_client
|
||||
|
||||
//func TestSmartSelect(t *testing.T) {
|
||||
// t.Skip("Refactor test")
|
||||
// test_utils.InitTestProvider(t, test_utils.TorrentClient())
|
||||
//
|
||||
// _ = t.TempDir()
|
||||
//
|
||||
// anilistClient := anilist.TestGetMockAnilistClient()
|
||||
// _ = anilist_platform.NewAnilistPlatform(anilistClient, util.NewLogger())
|
||||
//
|
||||
// // get repo
|
||||
//
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// mediaId int
|
||||
// url string
|
||||
// selectedEpisodes []int
|
||||
// client string
|
||||
// }{
|
||||
// {
|
||||
// name: "Kakegurui xx (Season 2)",
|
||||
// mediaId: 100876,
|
||||
// url: "https://nyaa.si/view/1553978", // kakegurui season 1 + season 2
|
||||
// selectedEpisodes: []int{10, 11, 12}, // should select 10, 11, 12 in season 2
|
||||
// client: QbittorrentClient,
|
||||
// },
|
||||
// {
|
||||
// name: "Spy x Family",
|
||||
// mediaId: 140960,
|
||||
// url: "https://nyaa.si/view/1661695", // spy x family (01-25)
|
||||
// selectedEpisodes: []int{10, 11, 12}, // should select 10, 11, 12
|
||||
// client: QbittorrentClient,
|
||||
// },
|
||||
// {
|
||||
// name: "Spy x Family Part 2",
|
||||
// mediaId: 142838,
|
||||
// url: "https://nyaa.si/view/1661695", // spy x family (01-25)
|
||||
// selectedEpisodes: []int{10, 11, 12, 13}, // should select 22, 23, 24, 25
|
||||
// client: QbittorrentClient,
|
||||
// },
|
||||
// {
|
||||
// name: "Kakegurui xx (Season 2)",
|
||||
// mediaId: 100876,
|
||||
// url: "https://nyaa.si/view/1553978", // kakegurui season 1 + season 2
|
||||
// selectedEpisodes: []int{10, 11, 12}, // should select 10, 11, 12 in season 2
|
||||
// client: TransmissionClient,
|
||||
// },
|
||||
// {
|
||||
// name: "Spy x Family",
|
||||
// mediaId: 140960,
|
||||
// url: "https://nyaa.si/view/1661695", // spy x family (01-25)
|
||||
// selectedEpisodes: []int{10, 11, 12}, // should select 10, 11, 12
|
||||
// client: TransmissionClient,
|
||||
// },
|
||||
// {
|
||||
// name: "Spy x Family Part 2",
|
||||
// mediaId: 142838,
|
||||
// url: "https://nyaa.si/view/1661695", // spy x family (01-25)
|
||||
// selectedEpisodes: []int{10, 11, 12, 13}, // should select 22, 23, 24, 25
|
||||
// client: TransmissionClient,
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// for _, tt := range tests {
|
||||
//
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
//
|
||||
// repo := getTestRepo(t, tt.client)
|
||||
//
|
||||
// ok := repo.Start()
|
||||
// if !assert.True(t, ok) {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// })
|
||||
//
|
||||
// }
|
||||
//
|
||||
//}
|
||||
@@ -0,0 +1,164 @@
|
||||
package torrent_client
|
||||
|
||||
import (
|
||||
"seanime/internal/torrent_clients/qbittorrent/model"
|
||||
"seanime/internal/util"
|
||||
|
||||
"github.com/hekmon/transmissionrpc/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
TorrentStatusDownloading TorrentStatus = "downloading"
|
||||
TorrentStatusSeeding TorrentStatus = "seeding"
|
||||
TorrentStatusPaused TorrentStatus = "paused"
|
||||
TorrentStatusOther TorrentStatus = "other"
|
||||
TorrentStatusStopped TorrentStatus = "stopped"
|
||||
)
|
||||
|
||||
type (
|
||||
Torrent struct {
|
||||
Name string `json:"name"`
|
||||
Hash string `json:"hash"`
|
||||
Seeds int `json:"seeds"`
|
||||
UpSpeed string `json:"upSpeed"`
|
||||
DownSpeed string `json:"downSpeed"`
|
||||
Progress float64 `json:"progress"`
|
||||
Size string `json:"size"`
|
||||
Eta string `json:"eta"`
|
||||
Status TorrentStatus `json:"status"`
|
||||
ContentPath string `json:"contentPath"`
|
||||
}
|
||||
TorrentStatus string
|
||||
)
|
||||
|
||||
//var torrentPool = util.NewPool[*Torrent](func() *Torrent {
|
||||
// return &Torrent{}
|
||||
//})
|
||||
|
||||
func (r *Repository) FromTransmissionTorrents(t []transmissionrpc.Torrent) []*Torrent {
|
||||
ret := make([]*Torrent, 0, len(t))
|
||||
for _, t := range t {
|
||||
ret = append(ret, r.FromTransmissionTorrent(&t))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (r *Repository) FromTransmissionTorrent(t *transmissionrpc.Torrent) *Torrent {
|
||||
torrent := &Torrent{}
|
||||
|
||||
torrent.Name = "N/A"
|
||||
if t.Name != nil {
|
||||
torrent.Name = *t.Name
|
||||
}
|
||||
|
||||
torrent.Hash = "N/A"
|
||||
if t.HashString != nil {
|
||||
torrent.Hash = *t.HashString
|
||||
}
|
||||
|
||||
torrent.Seeds = 0
|
||||
if t.PeersSendingToUs != nil {
|
||||
torrent.Seeds = int(*t.PeersSendingToUs)
|
||||
}
|
||||
|
||||
torrent.UpSpeed = "0 KB/s"
|
||||
if t.RateUpload != nil {
|
||||
torrent.UpSpeed = util.ToHumanReadableSpeed(int(*t.RateUpload))
|
||||
}
|
||||
|
||||
torrent.DownSpeed = "0 KB/s"
|
||||
if t.RateDownload != nil {
|
||||
torrent.DownSpeed = util.ToHumanReadableSpeed(int(*t.RateDownload))
|
||||
}
|
||||
|
||||
torrent.Progress = 0.0
|
||||
if t.PercentDone != nil {
|
||||
torrent.Progress = *t.PercentDone
|
||||
}
|
||||
|
||||
torrent.Size = "N/A"
|
||||
if t.TotalSize != nil {
|
||||
torrent.Size = util.Bytes(uint64(*t.TotalSize))
|
||||
}
|
||||
|
||||
torrent.Eta = "???"
|
||||
if t.ETA != nil {
|
||||
torrent.Eta = util.FormatETA(int(*t.ETA))
|
||||
}
|
||||
|
||||
torrent.ContentPath = ""
|
||||
if t.DownloadDir != nil {
|
||||
torrent.ContentPath = *t.DownloadDir
|
||||
}
|
||||
|
||||
torrent.Status = TorrentStatusOther
|
||||
if t.Status != nil && t.IsFinished != nil {
|
||||
torrent.Status = fromTransmissionTorrentStatus(*t.Status, *t.IsFinished)
|
||||
}
|
||||
|
||||
return torrent
|
||||
}
|
||||
|
||||
// fromTransmissionTorrentStatus returns a normalized status for the torrent.
|
||||
func fromTransmissionTorrentStatus(st transmissionrpc.TorrentStatus, isFinished bool) TorrentStatus {
|
||||
if st == transmissionrpc.TorrentStatusSeed || st == transmissionrpc.TorrentStatusSeedWait {
|
||||
return TorrentStatusSeeding
|
||||
} else if st == transmissionrpc.TorrentStatusStopped && isFinished {
|
||||
return TorrentStatusStopped
|
||||
} else if st == transmissionrpc.TorrentStatusStopped && !isFinished {
|
||||
return TorrentStatusPaused
|
||||
} else if st == transmissionrpc.TorrentStatusDownload || st == transmissionrpc.TorrentStatusDownloadWait {
|
||||
return TorrentStatusDownloading
|
||||
} else {
|
||||
return TorrentStatusOther
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Repository) FromQbitTorrents(t []*qbittorrent_model.Torrent) []*Torrent {
|
||||
ret := make([]*Torrent, 0, len(t))
|
||||
for _, t := range t {
|
||||
ret = append(ret, r.FromQbitTorrent(t))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
func (r *Repository) FromQbitTorrent(t *qbittorrent_model.Torrent) *Torrent {
|
||||
torrent := &Torrent{}
|
||||
|
||||
torrent.Name = t.Name
|
||||
torrent.Hash = t.Hash
|
||||
torrent.Seeds = t.NumSeeds
|
||||
torrent.UpSpeed = util.ToHumanReadableSpeed(t.Upspeed)
|
||||
torrent.DownSpeed = util.ToHumanReadableSpeed(t.Dlspeed)
|
||||
torrent.Progress = t.Progress
|
||||
torrent.Size = util.Bytes(uint64(t.Size))
|
||||
torrent.Eta = util.FormatETA(t.Eta)
|
||||
torrent.ContentPath = t.ContentPath
|
||||
torrent.Status = fromQbitTorrentStatus(t.State)
|
||||
|
||||
return torrent
|
||||
}
|
||||
|
||||
// fromQbitTorrentStatus returns a normalized status for the torrent.
|
||||
func fromQbitTorrentStatus(st qbittorrent_model.TorrentState) TorrentStatus {
|
||||
if st == qbittorrent_model.StateQueuedUP ||
|
||||
st == qbittorrent_model.StateStalledUP ||
|
||||
st == qbittorrent_model.StateForcedUP ||
|
||||
st == qbittorrent_model.StateCheckingUP ||
|
||||
st == qbittorrent_model.StateUploading {
|
||||
return TorrentStatusSeeding
|
||||
} else if st == qbittorrent_model.StatePausedDL || st == qbittorrent_model.StateStoppedDL {
|
||||
return TorrentStatusPaused
|
||||
} else if st == qbittorrent_model.StateDownloading ||
|
||||
st == qbittorrent_model.StateCheckingDL ||
|
||||
st == qbittorrent_model.StateStalledDL ||
|
||||
st == qbittorrent_model.StateQueuedDL ||
|
||||
st == qbittorrent_model.StateMetaDL ||
|
||||
st == qbittorrent_model.StateAllocating ||
|
||||
st == qbittorrent_model.StateForceDL {
|
||||
return TorrentStatusDownloading
|
||||
} else if st == qbittorrent_model.StatePausedUP || st == qbittorrent_model.StateStoppedUP {
|
||||
return TorrentStatusStopped
|
||||
} else {
|
||||
return TorrentStatusOther
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user