From 08dd79ff478efd0e79e5b1e09f5aed27aa142c1e Mon Sep 17 00:00:00 2001 From: bytedream Date: Mon, 17 Jan 2022 15:22:18 +0100 Subject: [PATCH] Removed structure utils api --- utils/structure.go | 693 --------------------------------------------- 1 file changed, 693 deletions(-) delete mode 100644 utils/structure.go diff --git a/utils/structure.go b/utils/structure.go deleted file mode 100644 index 127a379..0000000 --- a/utils/structure.go +++ /dev/null @@ -1,693 +0,0 @@ -package utils - -import ( - "errors" - "github.com/ByteDream/crunchyroll-go" - "sync" -) - -// StructureError is the error type which is thrown whenever a structure fails -// to receive information (formats, episodes, ...) from the api endpoint -type StructureError struct { - error -} - -func IsStructureError(err error) (ok bool) { - if err != nil { - _, ok = err.(*StructureError) - } - return -} - -// FormatStructure is the basic structure which every other structure implements. -// With it, and all other structures the api usage can be simplified magnificent -type FormatStructure struct { - // initState is true if every format, stream, ... in the structure tree is initialized - initState bool - - // getFunc specified the function which will be called if crunchyroll.Format is empty / not initialized yet. - // It returns the formats itself, the parent streams (might be nil) and an error if one occurs - getFunc func() ([]*crunchyroll.Format, []*crunchyroll.Stream, error) - // formats holds all formats which were given - formats []*crunchyroll.Format - // parents holds all parents which were given - parents []*crunchyroll.Stream -} - -func newFormatStructure(parentStructure *StreamStructure) *FormatStructure { - return &FormatStructure{ - getFunc: func() (formats []*crunchyroll.Format, parents []*crunchyroll.Stream, err error) { - streams, err := parentStructure.Streams() - if err != nil { - return - } - - var wg sync.WaitGroup - var lock sync.Mutex - - for _, stream := range streams { - wg.Add(1) - stream := stream - go func() { - defer wg.Done() - f, err := stream.Formats() - if err != nil { - errors.As(err, &StructureError{}) - return - } - lock.Lock() - defer lock.Unlock() - for _, format := range f { - formats = append(formats, format) - parents = append(parents, stream) - } - }() - } - wg.Wait() - return - }, - } -} - -// NewFormatStructure returns a new FormatStructure, based on the given formats -func NewFormatStructure(formats []*crunchyroll.Format) *FormatStructure { - return &FormatStructure{ - getFunc: func() ([]*crunchyroll.Format, []*crunchyroll.Stream, error) { - return formats, nil, nil - }, - } -} - -// Formats returns all stored formats -func (fs *FormatStructure) Formats() ([]*crunchyroll.Format, error) { - var err error - if fs.formats == nil { - if fs.formats, fs.parents, err = fs.getFunc(); err != nil { - return nil, err - } - fs.initState = true - } - return fs.formats, nil -} - -// FormatParent returns the parent stream of a format (if present). -// If the format or parent is not stored, an error will be returned -func (fs *FormatStructure) FormatParent(format *crunchyroll.Format) (*crunchyroll.Stream, error) { - formats, err := fs.Formats() - if err != nil { - return nil, err - } - - if fs.parents == nil { - return nil, errors.New("no parents are given") - } - - for i, f := range formats { - if f == format { - return fs.parents[i], nil - } - } - return nil, errors.New("given format could not be found") -} - -// InitAll recursive requests all given information. -// All functions of FormatStructure or other structs in this file which are executed after this have a much lesser chance to return any error, -// so the error return value of these functions can be pretty safely ignored. -// This function should only be called if you need to the access to any function of FormatStructure which returns a crunchyroll.Format (or an array of it). -// Re-calling this method can lead to heavy problems (believe me, it caused a simple bug and i've tried to fix it for several hours). -// Check FormatStructure.InitAllState if you can call this method without causing bugs -func (fs *FormatStructure) InitAll() error { - var err error - if fs.formats, fs.parents, err = fs.getFunc(); err != nil { - return err - } - fs.initState = true - return nil -} - -// InitAllState returns FormatStructure.InitAll or FormatStructure.Formats was called. -// If so, all errors which are returned by functions of structs in this file can be safely ignored -func (fs *FormatStructure) InitAllState() bool { - return fs.initState -} - -// AvailableLocales returns all available audio, subtitle and hardsub locales for all formats. -// If includeEmpty is given, locales with no value are included too -func (fs *FormatStructure) AvailableLocales(includeEmpty bool) (audioLocales []crunchyroll.LOCALE, subtitleLocales []crunchyroll.LOCALE, hardsubLocales []crunchyroll.LOCALE, err error) { - var formats []*crunchyroll.Format - if formats, err = fs.Formats(); err != nil { - return - } - - audioMap := map[crunchyroll.LOCALE]interface{}{} - subtitleMap := map[crunchyroll.LOCALE]interface{}{} - hardsubMap := map[crunchyroll.LOCALE]interface{}{} - for _, format := range formats { - // audio locale should always have a valid locale - if includeEmpty || !includeEmpty && format.AudioLocale != "" { - audioMap[format.AudioLocale] = nil - } - if format.Subtitles != nil { - for _, subtitle := range format.Subtitles { - if subtitle.Locale == "" && !includeEmpty { - continue - } - subtitleMap[subtitle.Locale] = nil - } - } - if includeEmpty || !includeEmpty && format.Hardsub != "" { - hardsubMap[format.Hardsub] = nil - } - } - - for k := range audioMap { - audioLocales = append(audioLocales, k) - } - for k := range subtitleMap { - subtitleLocales = append(subtitleLocales, k) - } - for k := range hardsubMap { - hardsubLocales = append(hardsubLocales, k) - } - return -} - -// FilterFormatsByAudio returns all formats which have the given locale as their audio locale -func (fs *FormatStructure) FilterFormatsByAudio(locale crunchyroll.LOCALE) (f []*crunchyroll.Format, err error) { - var formats []*crunchyroll.Format - if formats, err = fs.Formats(); err != nil { - return nil, err - } - for _, format := range formats { - if format.AudioLocale == locale { - f = append(f, format) - } - } - return -} - -// FilterFormatsBySubtitle returns all formats which have the given locale as their subtitle locale. -// Hardsub indicates if the subtitle should be shown on the video itself -func (fs *FormatStructure) FilterFormatsBySubtitle(locale crunchyroll.LOCALE, hardsub bool) (f []*crunchyroll.Format, err error) { - var formats []*crunchyroll.Format - if formats, err = fs.Formats(); err != nil { - return nil, err - } - for _, format := range formats { - if hardsub && format.Hardsub == locale { - f = append(f, format) - } else if !hardsub && format.Hardsub == "" { - f = append(f, format) - } - } - return -} - -// FilterFormatsByLocales returns all formats which have the given locales as their property. -// Hardsub is the same as in FormatStructure.FilterFormatsBySubtitle -func (fs *FormatStructure) FilterFormatsByLocales(audioLocale, subtitleLocale crunchyroll.LOCALE, hardsub bool) ([]*crunchyroll.Format, error) { - var f []*crunchyroll.Format - - formats, err := fs.Formats() - if err != nil { - return nil, err - } - for _, format := range formats { - if format.AudioLocale == audioLocale { - if hardsub && format.Hardsub == subtitleLocale { - f = append(f, format) - } else if !hardsub && format.Hardsub == "" { - f = append(f, format) - } - } - } - if len(f) == 0 { - return nil, errors.New("could not find any matching format") - } - return f, nil -} - -// OrderFormatsByID loops through all stored formats and returns a 2d slice -// where a row represents an id and the column all formats which have this id -func (fs *FormatStructure) OrderFormatsByID() ([][]*crunchyroll.Format, error) { - formats, err := fs.Formats() - if err != nil { - return nil, err - } - - formatsMap := map[string][]*crunchyroll.Format{} - for _, format := range formats { - if _, ok := formatsMap[format.ID]; !ok { - formatsMap[format.ID] = make([]*crunchyroll.Format, 0) - } - formatsMap[format.ID] = append(formatsMap[format.ID], format) - } - - var orderedFormats [][]*crunchyroll.Format - for _, v := range formatsMap { - var f []*crunchyroll.Format - for _, format := range v { - f = append(f, format) - } - orderedFormats = append(orderedFormats, f) - } - return orderedFormats, nil -} - -// StreamStructure fields are nearly same as FormatStructure -type StreamStructure struct { - *FormatStructure - - getFunc func() ([]*crunchyroll.Stream, []crunchyroll.Video, error) - streams []*crunchyroll.Stream - parents []crunchyroll.Video -} - -func newStreamStructure(structure VideoStructure) *StreamStructure { - var getFunc func() (streams []*crunchyroll.Stream, parents []crunchyroll.Video, err error) - switch structure.(type) { - case *EpisodeStructure: - episodeStructure := structure.(*EpisodeStructure) - getFunc = func() (streams []*crunchyroll.Stream, parents []crunchyroll.Video, err error) { - episodes, err := episodeStructure.Episodes() - if err != nil { - return - } - - var wg sync.WaitGroup - var lock sync.Mutex - - for _, episode := range episodes { - wg.Add(1) - episode := episode - go func() { - defer wg.Done() - s, err := episode.Streams() - if err != nil { - errors.As(err, &StructureError{}) - return - } - lock.Lock() - defer lock.Unlock() - for _, stream := range s { - streams = append(streams, stream) - parents = append(parents, episode) - } - }() - } - wg.Wait() - return - } - case *MovieListingStructure: - movieListingStructure := structure.(*MovieListingStructure) - getFunc = func() (streams []*crunchyroll.Stream, parents []crunchyroll.Video, err error) { - movieListings, err := movieListingStructure.MovieListings() - if err != nil { - return - } - - var wg sync.WaitGroup - var lock sync.Mutex - - for _, movieListing := range movieListings { - wg.Add(1) - movieListing := movieListing - go func() { - defer wg.Done() - s, err := movieListing.Streams() - if err != nil { - errors.As(err, &StructureError{}) - return - } - lock.Lock() - defer lock.Unlock() - for _, stream := range s { - streams = append(streams, stream) - parents = append(parents, movieListing) - } - }() - } - wg.Wait() - return - } - } - - ss := &StreamStructure{ - getFunc: getFunc, - } - ss.FormatStructure = newFormatStructure(ss) - return ss -} - -// NewStreamStructure returns a new StreamStructure, based on the given formats -func NewStreamStructure(streams []*crunchyroll.Stream) *StreamStructure { - ss := &StreamStructure{ - getFunc: func() ([]*crunchyroll.Stream, []crunchyroll.Video, error) { - return streams, nil, nil - }, - } - ss.FormatStructure = newFormatStructure(ss) - return ss -} - -// Streams returns all stored streams -func (ss *StreamStructure) Streams() ([]*crunchyroll.Stream, error) { - if ss.streams == nil { - var err error - if ss.streams, ss.parents, err = ss.getFunc(); err != nil { - return nil, err - } - } - return ss.streams, nil -} - -// StreamParent returns the parent video (type crunchyroll.Series or crunchyroll.Movie) of a stream (if present). -// If the stream or parent is not stored, an error will be returned -func (ss *StreamStructure) StreamParent(stream *crunchyroll.Stream) (crunchyroll.Video, error) { - streams, err := ss.Streams() - if err != nil { - return nil, err - } - - if ss.parents == nil { - return nil, errors.New("no parents are given") - } - - for i, s := range streams { - if s == stream { - return ss.parents[i], nil - } - } - return nil, errors.New("given stream could not be found") -} - -// VideoStructure is an interface which is implemented by EpisodeStructure and MovieListingStructure -type VideoStructure interface{} - -// EpisodeStructure fields are nearly same as FormatStructure -type EpisodeStructure struct { - VideoStructure - *StreamStructure - - getFunc func() ([]*crunchyroll.Episode, []*crunchyroll.Season, error) - episodes []*crunchyroll.Episode - parents []*crunchyroll.Season -} - -func newEpisodeStructure(structure *SeasonStructure) *EpisodeStructure { - es := &EpisodeStructure{ - getFunc: func() (episodes []*crunchyroll.Episode, parents []*crunchyroll.Season, err error) { - seasons, err := structure.Seasons() - if err != nil { - return - } - - var wg sync.WaitGroup - var lock sync.Mutex - - for _, season := range seasons { - wg.Add(1) - season := season - go func() { - defer wg.Done() - e, err := season.Episodes() - if err != nil { - errors.As(err, &StructureError{}) - return - } - lock.Lock() - defer lock.Unlock() - for _, episode := range e { - episodes = append(episodes, episode) - parents = append(parents, season) - } - }() - } - wg.Wait() - return - }, - } - es.StreamStructure = newStreamStructure(es) - return es -} - -// NewEpisodeStructure returns a new EpisodeStructure, based on the given formats -func NewEpisodeStructure(episodes []*crunchyroll.Episode) *EpisodeStructure { - es := &EpisodeStructure{ - getFunc: func() ([]*crunchyroll.Episode, []*crunchyroll.Season, error) { - return episodes, nil, nil - }, - } - es.StreamStructure = newStreamStructure(es) - return es -} - -// Episodes returns all stored episodes -func (es *EpisodeStructure) Episodes() ([]*crunchyroll.Episode, error) { - if es.episodes == nil { - var err error - if es.episodes, es.parents, err = es.getFunc(); err != nil { - return nil, err - } - } - return es.episodes, nil -} - -// EpisodeParent returns the parent season of a stream (if present). -// If the stream or parent is not stored, an error will be returned -func (es *EpisodeStructure) EpisodeParent(episode *crunchyroll.Episode) (*crunchyroll.Season, error) { - episodes, err := es.Episodes() - if err != nil { - return nil, err - } - - if es.parents == nil { - return nil, errors.New("no parents are given") - } - - for i, e := range episodes { - if e == episode { - return es.parents[i], nil - } - } - return nil, errors.New("given episode could not be found") -} - -// GetEpisodeByFormat returns the episode to which the given format belongs to. -// If the format or the parent is not stored, an error will be returned -func (es *EpisodeStructure) GetEpisodeByFormat(format *crunchyroll.Format) (*crunchyroll.Episode, error) { - if !es.initState { - if err := es.InitAll(); err != nil { - return nil, err - } - } - - formatParent, err := es.FormatParent(format) - if err != nil { - return nil, err - } - streamParent, err := es.StreamParent(formatParent) - if err != nil { - return nil, err - } - episode, ok := streamParent.(*crunchyroll.Episode) - if !ok { - return nil, errors.New("could not find parent episode") - } - return episode, nil -} - -// GetEpisodeByURL returns an episode by its url -func (es *EpisodeStructure) GetEpisodeByURL(url string) (*crunchyroll.Episode, error) { - _, title, episodeNumber, _, ok := crunchyroll.ParseEpisodeURL(url) - if !ok { - if episodeid, ok := crunchyroll.ParseBetaEpisodeURL(url); ok { - episodes, err := es.Episodes() - if err != nil { - return nil, err - } - - for _, episode := range episodes { - if episode.ID == episodeid { - return episode, nil - } - } - } - - return nil, errors.New("invalid url") - } - - episodes, err := es.Episodes() - if err != nil { - return nil, err - } - - for _, episode := range episodes { - if episode.SlugTitle == title { - return episode, nil - } - } - - for _, episode := range episodes { - if episode.EpisodeNumber == episodeNumber { - return episode, nil - } - } - return nil, errors.New("no episode could be found") -} - -// OrderEpisodeByID orders episodes by their ids -func (es *EpisodeStructure) OrderEpisodeByID() ([][]*crunchyroll.Episode, error) { - episodes, err := es.Episodes() - if err != nil { - return nil, err - } - - episodesMap := map[string][]*crunchyroll.Episode{} - for _, episode := range episodes { - if _, ok := episodesMap[episode.ID]; !ok { - episodesMap[episode.ID] = make([]*crunchyroll.Episode, 0) - } - episodesMap[episode.ID] = append(episodesMap[episode.ID], episode) - } - - var orderedEpisodes [][]*crunchyroll.Episode - for _, v := range episodesMap { - orderedEpisodes = append(orderedEpisodes, v) - } - return orderedEpisodes, nil -} - -// OrderFormatsByEpisodeNumber orders episodes by their episode number. -// Episode number 1 is on position 1 in the slice, number 2 on position 2, and so on. -// This was made intentionally because there is a chance that episodes with the episode number 0 are existing -// and position 0 in the slice is reserved for them. -// Therefore, if the first episode number is, for example, 20, the first 19 array entries will be nil -func (es *EpisodeStructure) OrderFormatsByEpisodeNumber() ([][]*crunchyroll.Format, error) { - formats, err := es.Formats() - if err != nil { - return nil, err - } - - formatsMap := map[int][]*crunchyroll.Format{} - for _, format := range formats { - stream, err := es.FormatParent(format) - if err != nil { - return nil, err - } - video, err := es.StreamParent(stream) - if err != nil { - return nil, err - } - - episode, ok := video.(*crunchyroll.Episode) - if !ok { - continue - } - if _, ok := formatsMap[episode.EpisodeNumber]; !ok { - formatsMap[episode.EpisodeNumber] = make([]*crunchyroll.Format, 0) - } - formatsMap[episode.EpisodeNumber] = append(formatsMap[episode.EpisodeNumber], format) - } - - var highest int - for key := range formatsMap { - if key > highest { - highest = key - } - } - - var orderedFormats [][]*crunchyroll.Format - for i := 0; i < highest+1; i++ { - if formats, ok := formatsMap[i]; ok { - orderedFormats = append(orderedFormats, formats) - } else { - // simply adds nil in case that no episode with number i exists - orderedFormats = append(orderedFormats, nil) - } - } - return orderedFormats, nil -} - -// SeasonStructure fields are nearly same as FormatStructure -type SeasonStructure struct { - *EpisodeStructure - - getFunc func() ([]*crunchyroll.Season, error) - seasons []*crunchyroll.Season -} - -// NewSeasonStructure returns a new SeasonStructure, based on the given formats -func NewSeasonStructure(seasons []*crunchyroll.Season) *SeasonStructure { - ss := &SeasonStructure{ - seasons: seasons, - } - ss.EpisodeStructure = newEpisodeStructure(ss) - return ss -} - -// Seasons returns all stored seasons -func (ss *SeasonStructure) Seasons() ([]*crunchyroll.Season, error) { - if ss.seasons == nil { - var err error - if ss.seasons, err = ss.getFunc(); err != nil { - return nil, err - } - } - return ss.seasons, nil -} - -// MovieListingStructure fields are nearly same as FormatStructure -type MovieListingStructure struct { - VideoStructure - *StreamStructure - - getFunc func() ([]*crunchyroll.MovieListing, error) - movieListings []*crunchyroll.MovieListing -} - -// NewMovieListingStructure returns a new MovieListingStructure, based on the given formats -func NewMovieListingStructure(movieListings []*crunchyroll.MovieListing) *MovieListingStructure { - ml := &MovieListingStructure{ - getFunc: func() ([]*crunchyroll.MovieListing, error) { - return movieListings, nil - }, - } - ml.StreamStructure = newStreamStructure(ml) - return ml -} - -// MovieListings returns all stored movie listings -func (ml *MovieListingStructure) MovieListings() ([]*crunchyroll.MovieListing, error) { - if ml.movieListings == nil { - var err error - if ml.movieListings, err = ml.getFunc(); err != nil { - return nil, err - } - } - return ml.movieListings, nil -} - -// GetMovieListingByFormat returns the movie listing to which the given format belongs to. -// If the format or the parent is not stored, an error will be returned -func (ml *MovieListingStructure) GetMovieListingByFormat(format *crunchyroll.Format) (*crunchyroll.MovieListing, error) { - if !ml.initState { - if err := ml.InitAll(); err != nil { - return nil, err - } - } - - formatParent, err := ml.FormatParent(format) - if err != nil { - return nil, err - } - streamParent, err := ml.StreamParent(formatParent) - if err != nil { - return nil, err - } - movieListing, ok := streamParent.(*crunchyroll.MovieListing) - if !ok { - return nil, errors.New("could not find parent movie listing") - } - return movieListing, nil -}