node build fixed
This commit is contained in:
350
seanime-2.9.10/internal/library/anime/entry_download_info.go
Normal file
350
seanime-2.9.10/internal/library/anime/entry_download_info.go
Normal file
@@ -0,0 +1,350 @@
|
||||
package anime
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"seanime/internal/api/anilist"
|
||||
"seanime/internal/api/metadata"
|
||||
"seanime/internal/hook"
|
||||
"strconv"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/sourcegraph/conc/pool"
|
||||
)
|
||||
|
||||
type (
|
||||
// EntryDownloadInfo is instantiated by the Entry
|
||||
EntryDownloadInfo struct {
|
||||
EpisodesToDownload []*EntryDownloadEpisode `json:"episodesToDownload"`
|
||||
CanBatch bool `json:"canBatch"`
|
||||
BatchAll bool `json:"batchAll"`
|
||||
HasInaccurateSchedule bool `json:"hasInaccurateSchedule"`
|
||||
Rewatch bool `json:"rewatch"`
|
||||
AbsoluteOffset int `json:"absoluteOffset"`
|
||||
}
|
||||
|
||||
EntryDownloadEpisode struct {
|
||||
EpisodeNumber int `json:"episodeNumber"`
|
||||
AniDBEpisode string `json:"aniDBEpisode"`
|
||||
Episode *Episode `json:"episode"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
NewEntryDownloadInfoOptions struct {
|
||||
// Media's local files
|
||||
LocalFiles []*LocalFile
|
||||
AnimeMetadata *metadata.AnimeMetadata
|
||||
Media *anilist.BaseAnime
|
||||
Progress *int
|
||||
Status *anilist.MediaListStatus
|
||||
MetadataProvider metadata.Provider
|
||||
}
|
||||
)
|
||||
|
||||
// NewEntryDownloadInfo returns a list of episodes to download or episodes for the torrent/debrid streaming views
|
||||
// based on the options provided.
|
||||
func NewEntryDownloadInfo(opts *NewEntryDownloadInfoOptions) (*EntryDownloadInfo, error) {
|
||||
|
||||
reqEvent := &AnimeEntryDownloadInfoRequestedEvent{
|
||||
LocalFiles: opts.LocalFiles,
|
||||
AnimeMetadata: opts.AnimeMetadata,
|
||||
Media: opts.Media,
|
||||
Progress: opts.Progress,
|
||||
Status: opts.Status,
|
||||
EntryDownloadInfo: &EntryDownloadInfo{},
|
||||
}
|
||||
|
||||
err := hook.GlobalHookManager.OnAnimeEntryDownloadInfoRequested().Trigger(reqEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if reqEvent.DefaultPrevented {
|
||||
return reqEvent.EntryDownloadInfo, nil
|
||||
}
|
||||
|
||||
opts.LocalFiles = reqEvent.LocalFiles
|
||||
opts.AnimeMetadata = reqEvent.AnimeMetadata
|
||||
opts.Media = reqEvent.Media
|
||||
opts.Progress = reqEvent.Progress
|
||||
opts.Status = reqEvent.Status
|
||||
|
||||
if *opts.Media.Status == anilist.MediaStatusNotYetReleased {
|
||||
return &EntryDownloadInfo{}, nil
|
||||
}
|
||||
if opts.AnimeMetadata == nil {
|
||||
return nil, errors.New("could not get anime metadata")
|
||||
}
|
||||
currentEpisodeCount := opts.Media.GetCurrentEpisodeCount()
|
||||
if currentEpisodeCount == -1 && opts.AnimeMetadata != nil {
|
||||
currentEpisodeCount = opts.AnimeMetadata.GetCurrentEpisodeCount()
|
||||
}
|
||||
if currentEpisodeCount == -1 {
|
||||
return nil, errors.New("could not get current media episode count")
|
||||
}
|
||||
|
||||
// +---------------------+
|
||||
// | Discrepancy |
|
||||
// +---------------------+
|
||||
|
||||
// Whether AniList includes episode 0 as part of main episodes, but AniDB does not, however AniDB has "S1"
|
||||
discrepancy := FindDiscrepancy(opts.Media, opts.AnimeMetadata)
|
||||
|
||||
// AniList is the source of truth for episode numbers
|
||||
epSlice := newEpisodeSlice(currentEpisodeCount)
|
||||
|
||||
// Handle discrepancies
|
||||
if discrepancy != DiscrepancyNone {
|
||||
|
||||
// If AniList includes episode 0 as part of main episodes, but AniDB does not, however AniDB has "S1"
|
||||
if discrepancy == DiscrepancyAniListCountsEpisodeZero {
|
||||
// Add "S1" to the beginning of the episode slice
|
||||
epSlice.trimEnd(1)
|
||||
epSlice.prepend(0, "S1")
|
||||
}
|
||||
|
||||
// If AniList includes specials, but AniDB does not
|
||||
if discrepancy == DiscrepancyAniListCountsSpecials {
|
||||
diff := currentEpisodeCount - opts.AnimeMetadata.GetMainEpisodeCount()
|
||||
epSlice.trimEnd(diff)
|
||||
for i := 0; i < diff; i++ {
|
||||
epSlice.add(currentEpisodeCount-i, "S"+strconv.Itoa(i+1))
|
||||
}
|
||||
}
|
||||
|
||||
// If AniDB has more episodes than AniList
|
||||
if discrepancy == DiscrepancyAniDBHasMore {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Filter out episodes not aired
|
||||
if opts.Media.NextAiringEpisode != nil {
|
||||
epSlice.filter(func(item *episodeSliceItem, index int) bool {
|
||||
// e.g. if the next airing episode is 13, then filter out episodes 14 and above
|
||||
return index+1 < opts.Media.NextAiringEpisode.Episode
|
||||
})
|
||||
}
|
||||
|
||||
// Get progress, if the media isn't in the user's list, progress is 0
|
||||
// If the media is completed, set progress is 0
|
||||
progress := 0
|
||||
if opts.Progress != nil {
|
||||
progress = *opts.Progress
|
||||
}
|
||||
if opts.Status != nil {
|
||||
if *opts.Status == anilist.MediaListStatusCompleted {
|
||||
progress = 0
|
||||
}
|
||||
}
|
||||
|
||||
hasInaccurateSchedule := false
|
||||
if opts.Media.NextAiringEpisode == nil && *opts.Media.Status == anilist.MediaStatusReleasing {
|
||||
hasInaccurateSchedule = true
|
||||
}
|
||||
|
||||
// Filter out episodes already watched (index+1 is the progress number)
|
||||
toDownloadSlice := epSlice.filterNew(func(item *episodeSliceItem, index int) bool {
|
||||
return index+1 > progress
|
||||
})
|
||||
|
||||
// This slice contains episode numbers that are not downloaded
|
||||
// The source of truth is AniDB, but we will handle discrepancies
|
||||
lfsEpSlice := newEpisodeSlice(0)
|
||||
if opts.LocalFiles != nil {
|
||||
// Get all episode numbers of main local files
|
||||
for _, lf := range opts.LocalFiles {
|
||||
if lf.Metadata.Type == LocalFileTypeMain {
|
||||
lfsEpSlice.add(lf.Metadata.Episode, lf.Metadata.AniDBEpisode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out downloaded episodes
|
||||
toDownloadSlice.filter(func(item *episodeSliceItem, index int) bool {
|
||||
isDownloaded := false
|
||||
for _, lf := range opts.LocalFiles {
|
||||
if lf.Metadata.Type != LocalFileTypeMain {
|
||||
continue
|
||||
}
|
||||
// If the file episode number matches that of the episode slice item
|
||||
if lf.Metadata.Episode == item.episodeNumber {
|
||||
isDownloaded = true
|
||||
}
|
||||
// If the slice episode number is 0 and the file is a main S1
|
||||
if discrepancy == DiscrepancyAniListCountsEpisodeZero && item.episodeNumber == 0 && lf.Metadata.AniDBEpisode == "S1" {
|
||||
isDownloaded = true
|
||||
}
|
||||
}
|
||||
|
||||
return !isDownloaded
|
||||
})
|
||||
|
||||
// +---------------------+
|
||||
// | EntryEpisode |
|
||||
// +---------------------+
|
||||
|
||||
// Generate `episodesToDownload` based on `toDownloadSlice`
|
||||
|
||||
// DEVNOTE: The EntryEpisode generated has inaccurate progress numbers since not local files are passed in
|
||||
|
||||
progressOffset := 0
|
||||
if discrepancy == DiscrepancyAniListCountsEpisodeZero {
|
||||
progressOffset = 1
|
||||
}
|
||||
|
||||
p := pool.NewWithResults[*EntryDownloadEpisode]()
|
||||
for _, ep := range toDownloadSlice.getSlice() {
|
||||
p.Go(func() *EntryDownloadEpisode {
|
||||
str := new(EntryDownloadEpisode)
|
||||
str.EpisodeNumber = ep.episodeNumber
|
||||
str.AniDBEpisode = ep.aniDBEpisode
|
||||
// Create a new episode with a placeholder local file
|
||||
// We pass that placeholder local file so that all episodes are hydrated as main episodes for consistency
|
||||
str.Episode = NewEpisode(&NewEpisodeOptions{
|
||||
LocalFile: &LocalFile{
|
||||
ParsedData: &LocalFileParsedData{},
|
||||
ParsedFolderData: []*LocalFileParsedData{},
|
||||
Metadata: &LocalFileMetadata{
|
||||
Episode: ep.episodeNumber,
|
||||
Type: LocalFileTypeMain,
|
||||
AniDBEpisode: ep.aniDBEpisode,
|
||||
},
|
||||
},
|
||||
OptionalAniDBEpisode: str.AniDBEpisode,
|
||||
AnimeMetadata: opts.AnimeMetadata,
|
||||
Media: opts.Media,
|
||||
ProgressOffset: progressOffset,
|
||||
IsDownloaded: false,
|
||||
MetadataProvider: opts.MetadataProvider,
|
||||
})
|
||||
str.Episode.AniDBEpisode = ep.aniDBEpisode
|
||||
// Reset the local file to nil, since it's a placeholder
|
||||
str.Episode.LocalFile = nil
|
||||
return str
|
||||
})
|
||||
}
|
||||
episodesToDownload := p.Wait()
|
||||
|
||||
//--------------
|
||||
|
||||
canBatch := false
|
||||
if *opts.Media.GetStatus() == anilist.MediaStatusFinished && opts.Media.GetTotalEpisodeCount() > 0 {
|
||||
canBatch = true
|
||||
}
|
||||
batchAll := false
|
||||
if canBatch && lfsEpSlice.len() == 0 && progress == 0 {
|
||||
batchAll = true
|
||||
}
|
||||
rewatch := false
|
||||
if opts.Status != nil && *opts.Status == anilist.MediaListStatusCompleted {
|
||||
rewatch = true
|
||||
}
|
||||
|
||||
downloadInfo := &EntryDownloadInfo{
|
||||
EpisodesToDownload: episodesToDownload,
|
||||
CanBatch: canBatch,
|
||||
BatchAll: batchAll,
|
||||
Rewatch: rewatch,
|
||||
HasInaccurateSchedule: hasInaccurateSchedule,
|
||||
AbsoluteOffset: opts.AnimeMetadata.GetOffset(),
|
||||
}
|
||||
|
||||
event := &AnimeEntryDownloadInfoEvent{
|
||||
EntryDownloadInfo: downloadInfo,
|
||||
}
|
||||
err = hook.GlobalHookManager.OnAnimeEntryDownloadInfo().Trigger(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return event.EntryDownloadInfo, nil
|
||||
}
|
||||
|
||||
type episodeSliceItem struct {
|
||||
episodeNumber int
|
||||
aniDBEpisode string
|
||||
}
|
||||
|
||||
type episodeSlice []*episodeSliceItem
|
||||
|
||||
func newEpisodeSlice(episodeCount int) *episodeSlice {
|
||||
s := make([]*episodeSliceItem, 0)
|
||||
for i := 0; i < episodeCount; i++ {
|
||||
s = append(s, &episodeSliceItem{episodeNumber: i + 1, aniDBEpisode: strconv.Itoa(i + 1)})
|
||||
}
|
||||
ret := &episodeSlice{}
|
||||
ret.set(s)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *episodeSlice) set(eps []*episodeSliceItem) {
|
||||
*s = eps
|
||||
}
|
||||
|
||||
func (s *episodeSlice) add(episodeNumber int, aniDBEpisode string) {
|
||||
*s = append(*s, &episodeSliceItem{episodeNumber: episodeNumber, aniDBEpisode: aniDBEpisode})
|
||||
}
|
||||
|
||||
func (s *episodeSlice) prepend(episodeNumber int, aniDBEpisode string) {
|
||||
*s = append([]*episodeSliceItem{{episodeNumber: episodeNumber, aniDBEpisode: aniDBEpisode}}, *s...)
|
||||
}
|
||||
|
||||
func (s *episodeSlice) trimEnd(n int) {
|
||||
*s = (*s)[:len(*s)-n]
|
||||
}
|
||||
|
||||
func (s *episodeSlice) trimStart(n int) {
|
||||
*s = (*s)[n:]
|
||||
}
|
||||
|
||||
func (s *episodeSlice) len() int {
|
||||
return len(*s)
|
||||
}
|
||||
|
||||
func (s *episodeSlice) get(index int) *episodeSliceItem {
|
||||
return (*s)[index]
|
||||
}
|
||||
|
||||
func (s *episodeSlice) getEpisodeNumber(episodeNumber int) *episodeSliceItem {
|
||||
for _, item := range *s {
|
||||
if item.episodeNumber == episodeNumber {
|
||||
return item
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *episodeSlice) filter(filter func(*episodeSliceItem, int) bool) {
|
||||
*s = lo.Filter(*s, filter)
|
||||
}
|
||||
|
||||
func (s *episodeSlice) filterNew(filter func(*episodeSliceItem, int) bool) *episodeSlice {
|
||||
s2 := make(episodeSlice, 0)
|
||||
for i, item := range *s {
|
||||
if filter(item, i) {
|
||||
s2 = append(s2, item)
|
||||
}
|
||||
}
|
||||
return &s2
|
||||
}
|
||||
|
||||
func (s *episodeSlice) copy() *episodeSlice {
|
||||
s2 := make(episodeSlice, len(*s), cap(*s))
|
||||
for i, item := range *s {
|
||||
s2[i] = item
|
||||
}
|
||||
return &s2
|
||||
}
|
||||
|
||||
func (s *episodeSlice) getSlice() []*episodeSliceItem {
|
||||
return *s
|
||||
}
|
||||
|
||||
func (s *episodeSlice) print() {
|
||||
for i, item := range *s {
|
||||
fmt.Printf("(%d) %d -> %s\n", i, item.episodeNumber, item.aniDBEpisode)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user