aboutsummaryrefslogtreecommitdiff
path: root/database/anime_cache.go
diff options
context:
space:
mode:
authorPriyansh <[email protected]>2025-08-19 14:13:01 +0530
committerPriyansh <[email protected]>2025-08-19 14:13:01 +0530
commitc336a3bab91186be1b998bba67e57d0797bd87ba (patch)
tree02a66a0d664c10107e809a4ac2b226398e6a4eb4 /database/anime_cache.go
parent9fbf28c2f77ec74c75a5274cbda897217ceaf571 (diff)
downloadmetachan-c336a3bab91186be1b998bba67e57d0797bd87ba.tar.xz
metachan-c336a3bab91186be1b998bba67e57d0797bd87ba.zip
fix foreign key constraints in anime entities
Diffstat (limited to 'database/anime_cache.go')
-rw-r--r--database/anime_cache.go1182
1 files changed, 426 insertions, 756 deletions
diff --git a/database/anime_cache.go b/database/anime_cache.go
index f26dc8b..6981b7a 100644
--- a/database/anime_cache.go
+++ b/database/anime_cache.go
@@ -2,10 +2,8 @@ package database
import (
"fmt"
- "metachan/config"
"metachan/entities"
"metachan/types"
- "metachan/utils/logger"
"strings"
"time"
@@ -13,15 +11,11 @@ import (
)
const (
- // CacheExpirationTime represents the duration after which the cache is considered stale
CacheExpirationTime = 24 * time.Hour
)
-// GetCachedAnimeByMALID retrieves an anime from the cache by MAL ID
func GetCachedAnimeByMALID(malID int) (*entities.CachedAnime, error) {
var anime entities.CachedAnime
-
- // Query the anime with all its relationships
err := DB.
Preload("Images").
Preload("Logos").
@@ -42,362 +36,508 @@ func GetCachedAnimeByMALID(malID int) (*entities.CachedAnime, error) {
Preload("Characters.VoiceActors").
Preload("AiringSchedule").
Preload("Seasons").
- Preload("Seasons.Images").
- Preload("Seasons.Scores").
- Preload("Seasons.AiringStatus").
- Preload("Seasons.AiringStatus.From").
- Preload("Seasons.AiringStatus.To").
Where("mal_id = ?", malID).
First(&anime).Error
if err != nil {
return nil, err
}
-
return &anime, nil
}
-// IsCacheValid checks if the cache is still valid based on the last update time
func IsCacheValid(anime *entities.CachedAnime) bool {
if anime == nil {
return false
}
-
- // Check if the cache has expired
return time.Since(anime.LastUpdated) < CacheExpirationTime
}
-// SaveAnimeToCache saves or updates an anime in the cache
func SaveAnimeToCache(animeData *types.Anime) error {
- // For SQLite, add retry logic to handle database locks
- var maxRetries = 5
- var retryDelay = 500 * time.Millisecond
-
- for attempt := 1; attempt <= maxRetries; attempt++ {
- err := saveAnimeToCacheWithRetry(animeData)
- if err == nil {
- logger.Log(fmt.Sprintf("Successfully saved anime (MAL ID: %d) to cache", animeData.MALID), logger.LogOptions{
- Level: logger.Success,
- Prefix: "AnimeCache",
- })
- return nil
- }
-
- // Check if it's a database lock error
- if strings.Contains(err.Error(), "database is locked") {
- logger.Log(fmt.Sprintf("Database locked (attempt %d/%d) for MAL ID %d: %v. Retrying in %v...",
- attempt, maxRetries, animeData.MALID, err, retryDelay), logger.LogOptions{
- Level: logger.Warn,
- Prefix: "AnimeCache",
- })
-
- time.Sleep(retryDelay)
- retryDelay *= 2 // Exponential backoff
- continue
- }
-
- // Non-lock error, just return it
- return err
+ if animeData == nil {
+ return fmt.Errorf("anime data is nil")
}
- return fmt.Errorf("failed to save anime (MAL ID: %d) after %d retries: database is locked",
- animeData.MALID, maxRetries)
-}
+ var tx *gorm.DB = DB.Begin()
+ if tx.Error != nil {
+ return tx.Error
+ }
-// saveAnimeToCacheWithRetry is the internal implementation of SaveAnimeToCache
-func saveAnimeToCacheWithRetry(animeData *types.Anime) error {
- // Start a transaction
- tx := DB.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
- if err := tx.Error; err != nil {
+ if err := tx.Exec("DELETE FROM cached_animes WHERE mal_id = ?", animeData.MALID).Error; err != nil {
+ tx.Rollback()
return err
}
- // Check if anime already exists in cache
- var existingAnime entities.CachedAnime
- result := tx.Where("mal_id = ?", animeData.MALID).First(&existingAnime)
-
- // If exists, delete the existing record and all its relations to avoid duplicates
- if result.Error == nil {
- // First directly delete the record with raw SQL to bypass GORM's hooks
- // which might be causing issues with constraints
- if err := tx.Exec("DELETE FROM cached_animes WHERE mal_id = ?", animeData.MALID).Error; err != nil {
- logger.Log(fmt.Sprintf("Failed to delete existing anime with direct SQL: %v", err), logger.LogOptions{
- Level: logger.Error,
- Prefix: "AnimeCache",
- })
+ anime := &entities.CachedAnime{
+ MALID: animeData.MALID,
+ TitleRomaji: animeData.Titles.Romaji,
+ TitleEnglish: animeData.Titles.English,
+ TitleJapanese: animeData.Titles.Japanese,
+ TitleSynonyms: strings.Join(animeData.Titles.Synonyms, ","),
+ Synopsis: animeData.Synopsis,
+ Type: string(animeData.Type),
+ Source: animeData.Source,
+ Airing: animeData.Airing,
+ Status: animeData.Status,
+ Duration: animeData.Duration,
+ Color: animeData.Color,
+ Season: animeData.Season,
+ Year: animeData.Year,
+ SubbedCount: animeData.Episodes.Subbed,
+ DubbedCount: animeData.Episodes.Dubbed,
+ TotalEpisodes: animeData.Episodes.Total,
+ AiredEpisodes: animeData.Episodes.Aired,
+ LastUpdated: time.Now(),
+ }
+
+ if err := tx.Create(anime).Error; err != nil {
+ tx.Rollback()
+ return err
+ }
+
+ // Now create related records with proper foreign key references
+ if animeData.Images.Small != "" || animeData.Images.Large != "" || animeData.Images.Original != "" {
+ images := &entities.CachedAnimeImages{
+ AnimeID: anime.ID,
+ Small: animeData.Images.Small,
+ Large: animeData.Images.Large,
+ Original: animeData.Images.Original,
+ }
+ if err := tx.Create(images).Error; err != nil {
tx.Rollback()
return err
}
+ }
- // Then also try the standard deleteExistingAnimeCache to clean up related records
- if err := deleteExistingAnimeCache(tx, existingAnime.ID); err != nil {
- logger.Log(fmt.Sprintf("Warning: Issue with deleteExistingAnimeCache: %v", err), logger.LogOptions{
- Level: logger.Warn,
- Prefix: "AnimeCache",
- })
- // Don't rollback here, we already deleted the main record which should address the constraint
+ if animeData.Logos.Small != "" || animeData.Logos.Medium != "" || animeData.Logos.Large != "" || animeData.Logos.XLarge != "" || animeData.Logos.Original != "" {
+ logos := &entities.CachedAnimeLogos{
+ AnimeID: anime.ID,
+ Small: animeData.Logos.Small,
+ Medium: animeData.Logos.Medium,
+ Large: animeData.Logos.Large,
+ XLarge: animeData.Logos.XLarge,
+ Original: animeData.Logos.Original,
+ }
+ if err := tx.Create(logos).Error; err != nil {
+ tx.Rollback()
+ return err
}
}
- // Create new cached anime
- cachedAnime := convertToCachedAnime(animeData)
-
- // Clear the ID to ensure we're creating a new record
- cachedAnime.ID = 0
-
- // Check if we're using SQLite and need to handle large datasets
- if config.Config.DatabaseDriver == types.SQLite && hasLargeDataset(cachedAnime) {
- // For SQLite with large datasets, save with batching
- if err := saveCachedAnimeWithBatching(tx, cachedAnime); err != nil {
+ if animeData.Covers.Small != "" || animeData.Covers.Large != "" || animeData.Covers.Original != "" {
+ covers := &entities.CachedAnimeCovers{
+ AnimeID: anime.ID,
+ Small: animeData.Covers.Small,
+ Large: animeData.Covers.Large,
+ Original: animeData.Covers.Original,
+ }
+ if err := tx.Create(covers).Error; err != nil {
tx.Rollback()
return err
}
- } else {
- // For other databases or small datasets, use standard GORM create
- if err := tx.Create(&cachedAnime).Error; err != nil {
+ }
+
+ if animeData.Scores.Score > 0 || animeData.Scores.ScoredBy > 0 {
+ scores := &entities.CachedAnimeScores{
+ AnimeID: anime.ID,
+ Score: animeData.Scores.Score,
+ ScoredBy: animeData.Scores.ScoredBy,
+ Rank: animeData.Scores.Rank,
+ Popularity: animeData.Scores.Popularity,
+ Members: animeData.Scores.Members,
+ Favorites: animeData.Scores.Favorites,
+ }
+ if err := tx.Create(scores).Error; err != nil {
tx.Rollback()
return err
}
}
- // Commit the transaction
- if err := tx.Commit().Error; err != nil {
- return err
+ if animeData.AiringStatus.From.Year > 0 || animeData.AiringStatus.From.String != "" || animeData.AiringStatus.To.Year > 0 || animeData.AiringStatus.To.String != "" || animeData.AiringStatus.String != "" {
+ var fromDateID, toDateID *uint
+
+ if animeData.AiringStatus.From.Year > 0 || animeData.AiringStatus.From.String != "" {
+ fromDate := &entities.CachedAiringStatusDates{
+ Day: animeData.AiringStatus.From.Day,
+ Month: animeData.AiringStatus.From.Month,
+ Year: animeData.AiringStatus.From.Year,
+ String: animeData.AiringStatus.From.String,
+ }
+ if err := tx.Create(fromDate).Error; err != nil {
+ tx.Rollback()
+ return err
+ }
+ fromDateID = &fromDate.ID
+ }
+
+ if animeData.AiringStatus.To.Year > 0 || animeData.AiringStatus.To.String != "" {
+ toDate := &entities.CachedAiringStatusDates{
+ Day: animeData.AiringStatus.To.Day,
+ Month: animeData.AiringStatus.To.Month,
+ Year: animeData.AiringStatus.To.Year,
+ String: animeData.AiringStatus.To.String,
+ }
+ if err := tx.Create(toDate).Error; err != nil {
+ tx.Rollback()
+ return err
+ }
+ toDateID = &toDate.ID
+ }
+
+ airingStatus := &entities.CachedAiringStatus{
+ AnimeID: anime.ID,
+ FromID: fromDateID,
+ ToID: toDateID,
+ String: animeData.AiringStatus.String,
+ }
+
+ if err := tx.Create(airingStatus).Error; err != nil {
+ tx.Rollback()
+ return err
+ }
}
- // Log at debug level to avoid duplicate success messages
- logger.Log(fmt.Sprintf("Successfully saved anime (MAL ID: %d) to cache", animeData.MALID), logger.LogOptions{
- Level: logger.Debug,
- Prefix: "AnimeCache",
- })
+ if animeData.Broadcast.Day != "" || animeData.Broadcast.Time != "" {
+ broadcast := &entities.CachedAnimeBroadcast{
+ AnimeID: anime.ID,
+ Day: animeData.Broadcast.Day,
+ Time: animeData.Broadcast.Time,
+ Timezone: animeData.Broadcast.Timezone,
+ String: animeData.Broadcast.String,
+ }
+ if err := tx.Create(broadcast).Error; err != nil {
+ tx.Rollback()
+ return err
+ }
+ }
- return nil
-}
+ if animeData.NextAiringEpisode.AiringAt > 0 && animeData.NextAiringEpisode.Episode > 0 {
+ nextEpisode := &entities.CachedNextEpisode{
+ AnimeID: anime.ID,
+ AiringAt: animeData.NextAiringEpisode.AiringAt,
+ Episode: animeData.NextAiringEpisode.Episode,
+ }
+ if err := tx.Create(nextEpisode).Error; err != nil {
+ tx.Rollback()
+ return err
+ }
+ }
-// deleteExistingAnimeCache deletes an anime and all its relations from the cache
-func deleteExistingAnimeCache(tx *gorm.DB, animeID uint) error {
- // Define table structures with their foreign keys
- tableRelations := map[string]struct {
- Table string
- ForeignKey string
- Special bool // If true, needs special handling
- }{
- "cached_anime_voice_actors": {"cached_anime_voice_actors", "character_id", true}, // Links to characters, not anime directly
- "cached_anime_characters": {"cached_anime_characters", "anime_id", false},
- "cached_episode_titles": {"cached_episode_titles", "episode_id", true}, // Links to episodes, not anime directly
- "cached_anime_single_episodes": {"cached_anime_single_episodes", "anime_id", false},
- "cached_next_episodes": {"cached_next_episodes", "anime_id", false},
- "cached_schedule_episodes": {"cached_schedule_episodes", "anime_id", false},
- "cached_anime_licensors": {"cached_anime_licensors", "anime_id", false},
- "cached_anime_studios": {"cached_anime_studios", "anime_id", false},
- "cached_anime_producers": {"cached_anime_producers", "anime_id", false},
- "cached_anime_genres": {"cached_anime_genres", "anime_id", false},
- "cached_anime_broadcasts": {"cached_anime_broadcasts", "anime_id", false},
- "cached_airing_status_dates": {"cached_airing_status_dates", "airing_status_id", true}, // Links to airing status, not anime directly
- "cached_airing_statuses": {"cached_airing_statuses", "anime_id", false},
- "cached_anime_scores": {"cached_anime_scores", "anime_id", false},
- "cached_anime_covers": {"cached_anime_covers", "anime_id", false},
- "cached_anime_logos": {"cached_anime_logos", "anime_id", false},
- "cached_anime_images": {"cached_anime_images", "anime_id", false},
- "cached_anime_seasons": {"cached_anime_seasons", "parent_anime_id", false}, // Uses parent_anime_id
- "cached_animes": {"cached_animes", "id", true}, // Uses id instead of anime_id
+ // Create array records
+ for _, genre := range animeData.Genres {
+ genreRecord := &entities.CachedAnimeGenre{
+ AnimeID: anime.ID,
+ Name: genre.Name,
+ GenreID: genre.GenreID,
+ URL: genre.URL,
+ }
+ if err := tx.Create(genreRecord).Error; err != nil {
+ tx.Rollback()
+ return err
+ }
}
- // First, find all the character IDs associated with this anime
- var characterIDs []uint
- if err := tx.Table("cached_anime_characters").Where("anime_id = ?", animeID).Pluck("id", &characterIDs).Error; err != nil {
- // If there's an error, it might be that the table doesn't exist yet
- logger.Log(fmt.Sprintf("Note: Could not find character IDs: %v", err), logger.LogOptions{
- Level: logger.Debug,
- Prefix: "AnimeCache",
- })
+ for _, producer := range animeData.Producers {
+ producerRecord := &entities.CachedAnimeProducer{
+ AnimeID: anime.ID,
+ Name: producer.Name,
+ ProducerID: producer.ProducerID,
+ URL: producer.URL,
+ }
+ if err := tx.Create(producerRecord).Error; err != nil {
+ tx.Rollback()
+ return err
+ }
}
- // Find all episode IDs associated with this anime
- var episodeIDs []uint
- if err := tx.Table("cached_anime_single_episodes").Where("anime_id = ?", animeID).Pluck("id", &episodeIDs).Error; err != nil {
- logger.Log(fmt.Sprintf("Note: Could not find episode IDs: %v", err), logger.LogOptions{
- Level: logger.Debug,
- Prefix: "AnimeCache",
- })
+ for _, studio := range animeData.Studios {
+ studioRecord := &entities.CachedAnimeStudio{
+ AnimeID: anime.ID,
+ Name: studio.Name,
+ StudioID: studio.StudioID,
+ URL: studio.URL,
+ }
+ if err := tx.Create(studioRecord).Error; err != nil {
+ tx.Rollback()
+ return err
+ }
}
- // Find all airing status IDs associated with this anime
- var airingStatusIDs []uint
- if err := tx.Table("cached_airing_statuses").Where("anime_id = ?", animeID).Pluck("id", &airingStatusIDs).Error; err != nil {
- logger.Log(fmt.Sprintf("Note: Could not find airing status IDs: %v", err), logger.LogOptions{
- Level: logger.Debug,
- Prefix: "AnimeCache",
- })
+ for _, licensor := range animeData.Licensors {
+ licensorRecord := &entities.CachedAnimeLicensor{
+ AnimeID: anime.ID,
+ Name: licensor.Name,
+ ProducerID: licensor.ProducerID,
+ URL: licensor.URL,
+ }
+ if err := tx.Create(licensorRecord).Error; err != nil {
+ tx.Rollback()
+ return err
+ }
}
- // Delete voice actors by character IDs
- if len(characterIDs) > 0 {
- if err := tx.Where("character_id IN ?", characterIDs).Delete(&entities.CachedAnimeVoiceActor{}).Error; err != nil {
- logger.Log(fmt.Sprintf("Failed to delete voice actors: %v", err), logger.LogOptions{
- Level: logger.Warn,
- Prefix: "AnimeCache",
- })
+ for _, episode := range animeData.AiringSchedule {
+ scheduleRecord := &entities.CachedScheduleEpisode{
+ AnimeID: anime.ID,
+ AiringAt: episode.AiringAt,
+ Episode: episode.Episode,
+ }
+ if err := tx.Create(scheduleRecord).Error; err != nil {
+ tx.Rollback()
+ return err
}
}
- // Delete episode titles by episode IDs
- if len(episodeIDs) > 0 {
- if err := tx.Where("episode_id IN ?", episodeIDs).Delete(&entities.CachedEpisodeTitles{}).Error; err != nil {
- logger.Log(fmt.Sprintf("Failed to delete episode titles: %v", err), logger.LogOptions{
- Level: logger.Warn,
- Prefix: "AnimeCache",
- })
+ for _, episode := range animeData.Episodes.Episodes {
+ titles := &entities.CachedEpisodeTitles{
+ English: episode.Titles.English,
+ Japanese: episode.Titles.Japanese,
+ Romaji: episode.Titles.Romaji,
+ }
+ if err := tx.Create(titles).Error; err != nil {
+ tx.Rollback()
+ return err
+ }
+
+ episodeRecord := &entities.CachedAnimeSingleEpisode{
+ AnimeID: anime.ID,
+ TitlesID: titles.ID,
+ Description: episode.Description,
+ Aired: episode.Aired,
+ Score: episode.Score,
+ Filler: episode.Filler,
+ Recap: episode.Recap,
+ ForumURL: episode.ForumURL,
+ URL: episode.URL,
+ ThumbnailURL: episode.ThumbnailURL,
+ }
+ if err := tx.Create(episodeRecord).Error; err != nil {
+ tx.Rollback()
+ return err
}
}
- // Delete airing status dates by airing status IDs
- if len(airingStatusIDs) > 0 {
- if err := tx.Where("airing_status_id IN ?", airingStatusIDs).Delete(&entities.CachedAiringStatusDates{}).Error; err != nil {
- logger.Log(fmt.Sprintf("Failed to delete airing status dates: %v", err), logger.LogOptions{
- Level: logger.Warn,
- Prefix: "AnimeCache",
- })
+ for _, character := range animeData.Characters {
+ charRecord := &entities.CachedAnimeCharacter{
+ AnimeID: anime.ID,
+ MALID: character.MALID,
+ URL: character.URL,
+ ImageURL: character.ImageURL,
+ Name: character.Name,
+ Role: character.Role,
+ }
+
+ if err := tx.Create(charRecord).Error; err != nil {
+ tx.Rollback()
+ return err
+ }
+
+ for _, va := range character.VoiceActors {
+ vaRecord := &entities.CachedAnimeVoiceActor{
+ CharacterID: charRecord.ID,
+ MALID: va.MALID,
+ URL: va.URL,
+ Image: va.Image,
+ Name: va.Name,
+ Language: va.Language,
+ }
+ if err := tx.Create(vaRecord).Error; err != nil {
+ tx.Rollback()
+ return err
+ }
}
}
- // Now delete the remaining tables with direct anime_id or parent_anime_id references
- for name, relation := range tableRelations {
- if relation.Special {
- continue // Skip special cases we've already handled
+ for _, season := range animeData.Seasons {
+ seasonRecord := &entities.CachedAnimeSeason{
+ ParentAnimeID: anime.ID,
+ MALID: season.MALID,
+ TitleRomaji: season.Titles.Romaji,
+ TitleEnglish: season.Titles.English,
+ TitleJapanese: season.Titles.Japanese,
+ TitleSynonyms: strings.Join(season.Titles.Synonyms, ","),
+ Synopsis: season.Synopsis,
+ Type: string(season.Type),
+ Source: season.Source,
+ Airing: season.Airing,
+ Status: season.Status,
+ Duration: season.Duration,
+ Season: season.Season,
+ Year: season.Year,
+ Current: season.Current,
}
- query := fmt.Sprintf("DELETE FROM %s WHERE %s = ?", relation.Table, relation.ForeignKey)
- if err := tx.Exec(query, animeID).Error; err != nil {
- logger.Log(fmt.Sprintf("Failed to delete from %s: %v", name, err), logger.LogOptions{
- Level: logger.Warn,
- Prefix: "AnimeCache",
- })
- // Continue anyway - don't stop the entire delete operation
+ if err := tx.Create(seasonRecord).Error; err != nil {
+ tx.Rollback()
+ return err
+ }
+
+ if season.Images.Small != "" || season.Images.Large != "" || season.Images.Original != "" {
+ seasonImages := &entities.CachedAnimeImages{
+ AnimeID: seasonRecord.ID,
+ Small: season.Images.Small,
+ Large: season.Images.Large,
+ Original: season.Images.Original,
+ }
+ if err := tx.Create(seasonImages).Error; err != nil {
+ tx.Rollback()
+ return err
+ }
+ }
+
+ if season.Scores.Score > 0 || season.Scores.ScoredBy > 0 {
+ seasonScores := &entities.CachedAnimeScores{
+ AnimeID: seasonRecord.ID,
+ Score: season.Scores.Score,
+ ScoredBy: season.Scores.ScoredBy,
+ Rank: season.Scores.Rank,
+ Popularity: season.Scores.Popularity,
+ Members: season.Scores.Members,
+ Favorites: season.Scores.Favorites,
+ }
+ if err := tx.Create(seasonScores).Error; err != nil {
+ tx.Rollback()
+ return err
+ }
}
}
- // Finally delete the anime itself
- if err := tx.Where("id = ?", animeID).Delete(&entities.CachedAnime{}).Error; err != nil {
- return fmt.Errorf("failed to delete cached anime with ID %d: %w", animeID, err)
+ if animeData.Mappings.AniDB > 0 || animeData.Mappings.Anilist > 0 || animeData.Mappings.AnimeCountdown > 0 || animeData.Mappings.AnimePlanet != "" || animeData.Mappings.AniSearch > 0 || animeData.Mappings.IMDB != "" || animeData.Mappings.Kitsu > 0 || animeData.Mappings.LiveChart > 0 || animeData.Mappings.NotifyMoe != "" || animeData.Mappings.Simkl > 0 || animeData.Mappings.TMDB > 0 || animeData.Mappings.TVDB > 0 {
+ mapping := &entities.AnimeMapping{
+ AniDB: animeData.Mappings.AniDB,
+ Anilist: animeData.Mappings.Anilist,
+ AnimeCountdown: animeData.Mappings.AnimeCountdown,
+ AnimePlanet: animeData.Mappings.AnimePlanet,
+ AniSearch: animeData.Mappings.AniSearch,
+ IMDB: animeData.Mappings.IMDB,
+ Kitsu: animeData.Mappings.Kitsu,
+ LiveChart: animeData.Mappings.LiveChart,
+ MAL: animeData.MALID,
+ NotifyMoe: animeData.Mappings.NotifyMoe,
+ Simkl: animeData.Mappings.Simkl,
+ TMDB: animeData.Mappings.TMDB,
+ TVDB: animeData.Mappings.TVDB,
+ }
+
+ if err := tx.Create(mapping).Error; err != nil {
+ tx.Rollback()
+ return err
+ }
}
- return nil
+ return tx.Commit().Error
}
-// ConvertToTypesAnime converts a cached anime to the types.Anime format
-func ConvertToTypesAnime(cachedAnime *entities.CachedAnime) *types.Anime {
- if cachedAnime == nil {
+func ConvertToTypesAnime(cached *entities.CachedAnime) *types.Anime {
+ if cached == nil {
return nil
}
anime := &types.Anime{
- MALID: cachedAnime.MALID,
+ MALID: cached.MALID,
Titles: types.AnimeTitles{
- Romaji: cachedAnime.TitleRomaji,
- English: cachedAnime.TitleEnglish,
- Japanese: cachedAnime.TitleJapanese,
- Synonyms: strings.Split(cachedAnime.TitleSynonyms, ","),
+ Romaji: cached.TitleRomaji,
+ English: cached.TitleEnglish,
+ Japanese: cached.TitleJapanese,
+ Synonyms: strings.Split(cached.TitleSynonyms, ","),
+ },
+ Synopsis: cached.Synopsis,
+ Type: types.AniSyncType(cached.Type),
+ Source: cached.Source,
+ Airing: cached.Airing,
+ Status: cached.Status,
+ Duration: cached.Duration,
+ Color: cached.Color,
+ Season: cached.Season,
+ Year: cached.Year,
+ Episodes: types.AnimeEpisodes{
+ Total: cached.TotalEpisodes,
+ Aired: cached.AiredEpisodes,
+ Subbed: cached.SubbedCount,
+ Dubbed: cached.DubbedCount,
},
- Synopsis: cachedAnime.Synopsis,
- Type: types.AniSyncType(cachedAnime.Type),
- Source: cachedAnime.Source,
- Airing: cachedAnime.Airing,
- Status: cachedAnime.Status,
- Duration: cachedAnime.Duration,
- Color: cachedAnime.Color,
- Season: cachedAnime.Season,
- Year: cachedAnime.Year,
}
- // Fill in Images
- if cachedAnime.Images != nil {
+ if cached.Images != nil {
anime.Images = types.AnimeImages{
- Small: cachedAnime.Images.Small,
- Large: cachedAnime.Images.Large,
- Original: cachedAnime.Images.Original,
+ Small: cached.Images.Small,
+ Large: cached.Images.Large,
+ Original: cached.Images.Original,
}
}
- // Fill in Logos
- if cachedAnime.Logos != nil {
+ if cached.Logos != nil {
anime.Logos = types.AnimeLogos{
- Small: cachedAnime.Logos.Small,
- Medium: cachedAnime.Logos.Medium,
- Large: cachedAnime.Logos.Large,
- XLarge: cachedAnime.Logos.XLarge,
- Original: cachedAnime.Logos.Original,
+ Small: cached.Logos.Small,
+ Medium: cached.Logos.Medium,
+ Large: cached.Logos.Large,
+ XLarge: cached.Logos.XLarge,
+ Original: cached.Logos.Original,
}
}
- // Fill in Covers
- if cachedAnime.Covers != nil {
+ if cached.Covers != nil {
anime.Covers = types.AnimeImages{
- Small: cachedAnime.Covers.Small,
- Large: cachedAnime.Covers.Large,
- Original: cachedAnime.Covers.Original,
+ Small: cached.Covers.Small,
+ Large: cached.Covers.Large,
+ Original: cached.Covers.Original,
}
}
- // Fill in Scores
- if cachedAnime.Scores != nil {
+ if cached.Scores != nil {
anime.Scores = types.AnimeScores{
- Score: cachedAnime.Scores.Score,
- ScoredBy: cachedAnime.Scores.ScoredBy,
- Rank: cachedAnime.Scores.Rank,
- Popularity: cachedAnime.Scores.Popularity,
- Members: cachedAnime.Scores.Members,
- Favorites: cachedAnime.Scores.Favorites,
+ Score: cached.Scores.Score,
+ ScoredBy: cached.Scores.ScoredBy,
+ Rank: cached.Scores.Rank,
+ Popularity: cached.Scores.Popularity,
+ Members: cached.Scores.Members,
+ Favorites: cached.Scores.Favorites,
}
}
- // Fill in AiringStatus
- if cachedAnime.AiringStatus != nil {
+ if cached.AiringStatus != nil {
airingStatus := types.AiringStatus{
- String: cachedAnime.AiringStatus.String,
+ String: cached.AiringStatus.String,
}
- if cachedAnime.AiringStatus.From != nil {
+ if cached.AiringStatus.From != nil {
airingStatus.From = types.AiringStatusDates{
- Day: cachedAnime.AiringStatus.From.Day,
- Month: cachedAnime.AiringStatus.From.Month,
- Year: cachedAnime.AiringStatus.From.Year,
- String: cachedAnime.AiringStatus.From.String,
+ Day: cached.AiringStatus.From.Day,
+ Month: cached.AiringStatus.From.Month,
+ Year: cached.AiringStatus.From.Year,
+ String: cached.AiringStatus.From.String,
}
}
- if cachedAnime.AiringStatus.To != nil {
+ if cached.AiringStatus.To != nil {
airingStatus.To = types.AiringStatusDates{
- Day: cachedAnime.AiringStatus.To.Day,
- Month: cachedAnime.AiringStatus.To.Month,
- Year: cachedAnime.AiringStatus.To.Year,
- String: cachedAnime.AiringStatus.To.String,
+ Day: cached.AiringStatus.To.Day,
+ Month: cached.AiringStatus.To.Month,
+ Year: cached.AiringStatus.To.Year,
+ String: cached.AiringStatus.To.String,
}
}
anime.AiringStatus = airingStatus
}
- // Fill in Broadcast
- if cachedAnime.Broadcast != nil {
+ if cached.Broadcast != nil {
anime.Broadcast = types.AnimeBroadcast{
- Day: cachedAnime.Broadcast.Day,
- Time: cachedAnime.Broadcast.Time,
- Timezone: cachedAnime.Broadcast.Timezone,
- String: cachedAnime.Broadcast.String,
+ Day: cached.Broadcast.Day,
+ Time: cached.Broadcast.Time,
+ Timezone: cached.Broadcast.Timezone,
+ String: cached.Broadcast.String,
}
}
- // Convert genres
- if len(cachedAnime.Genres) > 0 {
- anime.Genres = make([]types.AnimeGenres, len(cachedAnime.Genres))
- for i, genre := range cachedAnime.Genres {
+ if len(cached.Genres) > 0 {
+ anime.Genres = make([]types.AnimeGenres, len(cached.Genres))
+ for i, genre := range cached.Genres {
anime.Genres[i] = types.AnimeGenres{
Name: genre.Name,
GenreID: genre.GenreID,
@@ -406,10 +546,9 @@ func ConvertToTypesAnime(cachedAnime *entities.CachedAnime) *types.Anime {
}
}
- // Convert producers
- if len(cachedAnime.Producers) > 0 {
- anime.Producers = make([]types.AnimeProducer, len(cachedAnime.Producers))
- for i, producer := range cachedAnime.Producers {
+ if len(cached.Producers) > 0 {
+ anime.Producers = make([]types.AnimeProducer, len(cached.Producers))
+ for i, producer := range cached.Producers {
anime.Producers[i] = types.AnimeProducer{
Name: producer.Name,
ProducerID: producer.ProducerID,
@@ -418,10 +557,9 @@ func ConvertToTypesAnime(cachedAnime *entities.CachedAnime) *types.Anime {
}
}
- // Convert studios
- if len(cachedAnime.Studios) > 0 {
- anime.Studios = make([]types.AnimeStudio, len(cachedAnime.Studios))
- for i, studio := range cachedAnime.Studios {
+ if len(cached.Studios) > 0 {
+ anime.Studios = make([]types.AnimeStudio, len(cached.Studios))
+ for i, studio := range cached.Studios {
anime.Studios[i] = types.AnimeStudio{
Name: studio.Name,
StudioID: studio.StudioID,
@@ -430,10 +568,9 @@ func ConvertToTypesAnime(cachedAnime *entities.CachedAnime) *types.Anime {
}
}
- // Convert licensors
- if len(cachedAnime.Licensors) > 0 {
- anime.Licensors = make([]types.AnimeLicensor, len(cachedAnime.Licensors))
- for i, licensor := range cachedAnime.Licensors {
+ if len(cached.Licensors) > 0 {
+ anime.Licensors = make([]types.AnimeLicensor, len(cached.Licensors))
+ for i, licensor := range cached.Licensors {
anime.Licensors[i] = types.AnimeLicensor{
Name: licensor.Name,
ProducerID: licensor.ProducerID,
@@ -442,18 +579,16 @@ func ConvertToTypesAnime(cachedAnime *entities.CachedAnime) *types.Anime {
}
}
- // Fill in NextAiringEpisode
- if cachedAnime.NextAiringEpisode != nil && cachedAnime.NextAiringEpisode.AiringAt > 0 && cachedAnime.NextAiringEpisode.Episode > 0 {
+ if cached.NextAiringEpisode != nil {
anime.NextAiringEpisode = types.AnimeAiringEpisode{
- AiringAt: cachedAnime.NextAiringEpisode.AiringAt,
- Episode: cachedAnime.NextAiringEpisode.Episode,
+ AiringAt: cached.NextAiringEpisode.AiringAt,
+ Episode: cached.NextAiringEpisode.Episode,
}
}
- // Convert airing schedule
- if len(cachedAnime.AiringSchedule) > 0 {
- anime.AiringSchedule = make([]types.AnimeAiringEpisode, len(cachedAnime.AiringSchedule))
- for i, episode := range cachedAnime.AiringSchedule {
+ if len(cached.AiringSchedule) > 0 {
+ anime.AiringSchedule = make([]types.AnimeAiringEpisode, len(cached.AiringSchedule))
+ for i, episode := range cached.AiringSchedule {
anime.AiringSchedule[i] = types.AnimeAiringEpisode{
AiringAt: episode.AiringAt,
Episode: episode.Episode,
@@ -461,11 +596,9 @@ func ConvertToTypesAnime(cachedAnime *entities.CachedAnime) *types.Anime {
}
}
- // Convert episodes
- if len(cachedAnime.Episodes) > 0 {
- anime.Episodes.Episodes = make([]types.AnimeSingleEpisode, len(cachedAnime.Episodes))
-
- for i, episode := range cachedAnime.Episodes {
+ if len(cached.Episodes) > 0 {
+ anime.Episodes.Episodes = make([]types.AnimeSingleEpisode, len(cached.Episodes))
+ for i, episode := range cached.Episodes {
episodeData := types.AnimeSingleEpisode{
Description: episode.Description,
Aired: episode.Aired,
@@ -487,17 +620,11 @@ func ConvertToTypesAnime(cachedAnime *entities.CachedAnime) *types.Anime {
anime.Episodes.Episodes[i] = episodeData
}
-
- anime.Episodes.Total = cachedAnime.TotalEpisodes
- anime.Episodes.Aired = cachedAnime.AiredEpisodes
- anime.Episodes.Subbed = cachedAnime.SubbedCount
- anime.Episodes.Dubbed = cachedAnime.DubbedCount
}
- // Convert characters
- if len(cachedAnime.Characters) > 0 {
- anime.Characters = make([]types.AnimeCharacter, len(cachedAnime.Characters))
- for i, character := range cachedAnime.Characters {
+ if len(cached.Characters) > 0 {
+ anime.Characters = make([]types.AnimeCharacter, len(cached.Characters))
+ for i, character := range cached.Characters {
animeCharacter := types.AnimeCharacter{
MALID: character.MALID,
URL: character.URL,
@@ -523,10 +650,9 @@ func ConvertToTypesAnime(cachedAnime *entities.CachedAnime) *types.Anime {
}
}
- // Convert seasons
- if len(cachedAnime.Seasons) > 0 {
- anime.Seasons = make([]types.AnimeSeason, len(cachedAnime.Seasons))
- for i, season := range cachedAnime.Seasons {
+ if len(cached.Seasons) > 0 {
+ anime.Seasons = make([]types.AnimeSeason, len(cached.Seasons))
+ for i, season := range cached.Seasons {
animeSeason := types.AnimeSeason{
MALID: season.MALID,
Synopsis: season.Synopsis,
@@ -595,479 +721,23 @@ func ConvertToTypesAnime(cachedAnime *entities.CachedAnime) *types.Anime {
}
}
- return anime
-}
-
-// convertToCachedAnime converts from types.Anime to entities.CachedAnime
-func convertToCachedAnime(animeData *types.Anime) *entities.CachedAnime {
- if animeData == nil {
- return nil
- }
-
- cachedAnime := &entities.CachedAnime{
- MALID: animeData.MALID,
- TitleRomaji: animeData.Titles.Romaji,
- TitleEnglish: animeData.Titles.English,
- TitleJapanese: animeData.Titles.Japanese,
- TitleSynonyms: strings.Join(animeData.Titles.Synonyms, ","),
- Synopsis: animeData.Synopsis,
- Type: string(animeData.Type),
- Source: animeData.Source,
- Airing: animeData.Airing,
- Status: animeData.Status,
- Duration: animeData.Duration,
- Color: animeData.Color,
- Season: animeData.Season,
- Year: animeData.Year,
- SubbedCount: animeData.Episodes.Subbed,
- DubbedCount: animeData.Episodes.Dubbed,
- TotalEpisodes: animeData.Episodes.Total,
- AiredEpisodes: animeData.Episodes.Aired,
- LastUpdated: time.Now(),
- }
-
- // Add Images
- cachedAnime.Images = &entities.CachedAnimeImages{
- Small: animeData.Images.Small,
- Large: animeData.Images.Large,
- Original: animeData.Images.Original,
- }
-
- // Add Logos
- cachedAnime.Logos = &entities.CachedAnimeLogos{
- Small: animeData.Logos.Small,
- Medium: animeData.Logos.Medium,
- Large: animeData.Logos.Large,
- XLarge: animeData.Logos.XLarge,
- Original: animeData.Logos.Original,
- }
-
- // Add Covers
- cachedAnime.Covers = &entities.CachedAnimeCovers{
- Small: animeData.Covers.Small,
- Large: animeData.Covers.Large,
- Original: animeData.Covers.Original,
- }
-
- // Add Scores
- cachedAnime.Scores = &entities.CachedAnimeScores{
- Score: animeData.Scores.Score,
- ScoredBy: animeData.Scores.ScoredBy,
- Rank: animeData.Scores.Rank,
- Popularity: animeData.Scores.Popularity,
- Members: animeData.Scores.Members,
- Favorites: animeData.Scores.Favorites,
- }
-
- // Add AiringStatus
- if animeData.AiringStatus.From.Year > 0 || animeData.AiringStatus.From.String != "" {
- cachedAnime.AiringStatus = &entities.CachedAiringStatus{
- String: animeData.AiringStatus.String,
- }
-
- if animeData.AiringStatus.From.Year > 0 || animeData.AiringStatus.From.String != "" {
- cachedAnime.AiringStatus.From = &entities.CachedAiringStatusDates{
- Day: animeData.AiringStatus.From.Day,
- Month: animeData.AiringStatus.From.Month,
- Year: animeData.AiringStatus.From.Year,
- String: animeData.AiringStatus.From.String,
- }
- }
-
- if animeData.AiringStatus.To.Year > 0 || animeData.AiringStatus.To.String != "" {
- cachedAnime.AiringStatus.To = &entities.CachedAiringStatusDates{
- Day: animeData.AiringStatus.To.Day,
- Month: animeData.AiringStatus.To.Month,
- Year: animeData.AiringStatus.To.Year,
- String: animeData.AiringStatus.To.String,
- }
- }
- }
-
- // Add Broadcast
- if animeData.Broadcast.Day != "" || animeData.Broadcast.Time != "" {
- cachedAnime.Broadcast = &entities.CachedAnimeBroadcast{
- Day: animeData.Broadcast.Day,
- Time: animeData.Broadcast.Time,
- Timezone: animeData.Broadcast.Timezone,
- String: animeData.Broadcast.String,
+ var mapping entities.AnimeMapping
+ if err := DB.Where("mal = ?", cached.MALID).First(&mapping).Error; err == nil {
+ anime.Mappings = types.AnimeMappings{
+ AniDB: mapping.AniDB,
+ Anilist: mapping.Anilist,
+ AnimeCountdown: mapping.AnimeCountdown,
+ AnimePlanet: mapping.AnimePlanet,
+ AniSearch: mapping.AniSearch,
+ IMDB: mapping.IMDB,
+ Kitsu: mapping.Kitsu,
+ LiveChart: mapping.LiveChart,
+ NotifyMoe: mapping.NotifyMoe,
+ Simkl: mapping.Simkl,
+ TMDB: mapping.TMDB,
+ TVDB: mapping.TVDB,
}
}
- // Add Genres
- if len(animeData.Genres) > 0 {
- cachedAnime.Genres = make([]entities.CachedAnimeGenre, len(animeData.Genres))
- for i, genre := range animeData.Genres {
- cachedAnime.Genres[i] = entities.CachedAnimeGenre{
- Name: genre.Name,
- GenreID: genre.GenreID,
- URL: genre.URL,
- }
- }
- }
-
- // Add Producers
- if len(animeData.Producers) > 0 {
- cachedAnime.Producers = make([]entities.CachedAnimeProducer, len(animeData.Producers))
- for i, producer := range animeData.Producers {
- cachedAnime.Producers[i] = entities.CachedAnimeProducer{
- Name: producer.Name,
- ProducerID: producer.ProducerID,
- URL: producer.URL,
- }
- }
- }
-
- // Add Studios
- if len(animeData.Studios) > 0 {
- cachedAnime.Studios = make([]entities.CachedAnimeStudio, len(animeData.Studios))
- for i, studio := range animeData.Studios {
- cachedAnime.Studios[i] = entities.CachedAnimeStudio{
- Name: studio.Name,
- StudioID: studio.StudioID,
- URL: studio.URL,
- }
- }
- }
-
- // Add Licensors
- if len(animeData.Licensors) > 0 {
- cachedAnime.Licensors = make([]entities.CachedAnimeLicensor, len(animeData.Licensors))
- for i, licensor := range animeData.Licensors {
- cachedAnime.Licensors[i] = entities.CachedAnimeLicensor{
- Name: licensor.Name,
- ProducerID: licensor.ProducerID,
- URL: licensor.URL,
- }
- }
- }
-
- // Get the current timestamp
- currentTime := time.Now().Unix()
-
- // Determine the next airing episode from the schedule
- var nextEpisode *types.AnimeAiringEpisode
-
- // Process next airing episode data - first check if there's a valid next airing episode directly provided
- if animeData.NextAiringEpisode.AiringAt > 0 && animeData.NextAiringEpisode.Episode > 0 {
- // Check if it's still in the future
- if int64(animeData.NextAiringEpisode.AiringAt) > currentTime {
- nextEpisode = &types.AnimeAiringEpisode{
- AiringAt: animeData.NextAiringEpisode.AiringAt,
- Episode: animeData.NextAiringEpisode.Episode,
- }
- }
- }
-
- // If we don't have a valid next episode yet, or the one we have has already aired,
- // scan the schedule to find the actual next episode
- if (nextEpisode == nil || nextEpisode.AiringAt == 0) && len(animeData.AiringSchedule) > 0 {
- // Sort the schedule by airing time
- sortedSchedule := make([]types.AnimeAiringEpisode, len(animeData.AiringSchedule))
- copy(sortedSchedule, animeData.AiringSchedule)
-
- // Sort by airing time
- for i := 0; i < len(sortedSchedule)-1; i++ {
- for j := i + 1; j < len(sortedSchedule); j++ {
- if sortedSchedule[i].AiringAt > sortedSchedule[j].AiringAt {
- sortedSchedule[i], sortedSchedule[j] = sortedSchedule[j], sortedSchedule[i]
- }
- }
- }
-
- // Find the first episode that hasn't aired yet
- for _, episode := range sortedSchedule {
- if int64(episode.AiringAt) > currentTime {
- nextEpisode = &types.AnimeAiringEpisode{
- AiringAt: episode.AiringAt,
- Episode: episode.Episode,
- }
- break
- }
- }
- }
-
- // Add NextAiringEpisode if we found a valid next episode
- if nextEpisode != nil && nextEpisode.AiringAt > 0 {
- cachedAnime.NextAiringEpisode = &entities.CachedNextEpisode{
- AiringAt: nextEpisode.AiringAt,
- Episode: nextEpisode.Episode,
- }
- logger.Log(fmt.Sprintf("Set next airing episode for %s (ID: %d): Episode %d at %s",
- cachedAnime.TitleRomaji, cachedAnime.MALID,
- nextEpisode.Episode,
- time.Unix(int64(nextEpisode.AiringAt), 0).Format(time.RFC3339)),
- logger.LogOptions{
- Level: logger.Debug,
- Prefix: "AnimeCache",
- })
- }
-
- // Add AiringSchedule
- if len(animeData.AiringSchedule) > 0 {
- cachedAnime.AiringSchedule = make([]entities.CachedScheduleEpisode, len(animeData.AiringSchedule))
- for i, episode := range animeData.AiringSchedule {
- cachedAnime.AiringSchedule[i] = entities.CachedScheduleEpisode{
- AiringAt: episode.AiringAt,
- Episode: episode.Episode,
- }
- }
- }
-
- // Add Episodes
- if len(animeData.Episodes.Episodes) > 0 {
- cachedAnime.Episodes = make([]entities.CachedAnimeSingleEpisode, len(animeData.Episodes.Episodes))
- for i, episode := range animeData.Episodes.Episodes {
- // Create episode titles
- titles := &entities.CachedEpisodeTitles{
- English: episode.Titles.English,
- Japanese: episode.Titles.Japanese,
- Romaji: episode.Titles.Romaji,
- }
-
- cachedAnime.Episodes[i] = entities.CachedAnimeSingleEpisode{
- Description: episode.Description,
- Aired: episode.Aired,
- Score: episode.Score,
- Filler: episode.Filler,
- Recap: episode.Recap,
- ForumURL: episode.ForumURL,
- URL: episode.URL,
- ThumbnailURL: episode.ThumbnailURL,
- Titles: titles,
- }
- }
- }
-
- // Add Characters
- if len(animeData.Characters) > 0 {
- cachedAnime.Characters = make([]entities.CachedAnimeCharacter, len(animeData.Characters))
- for i, character := range animeData.Characters {
- char := entities.CachedAnimeCharacter{
- MALID: character.MALID,
- URL: character.URL,
- ImageURL: character.ImageURL,
- Name: character.Name,
- Role: character.Role,
- }
-
- if len(character.VoiceActors) > 0 {
- char.VoiceActors = make([]entities.CachedAnimeVoiceActor, len(character.VoiceActors))
- for j, va := range character.VoiceActors {
- char.VoiceActors[j] = entities.CachedAnimeVoiceActor{
- MALID: va.MALID,
- URL: va.URL,
- Image: va.Image,
- Name: va.Name,
- Language: va.Language,
- }
- }
- }
- cachedAnime.Characters[i] = char
- }
- }
-
- // Add Seasons
- if len(animeData.Seasons) > 0 {
- cachedAnime.Seasons = make([]entities.CachedAnimeSeason, len(animeData.Seasons))
- for i, season := range animeData.Seasons {
- cachedSeason := entities.CachedAnimeSeason{
- ParentAnimeID: cachedAnime.ID,
- MALID: season.MALID,
- TitleRomaji: season.Titles.Romaji,
- TitleEnglish: season.Titles.English,
- TitleJapanese: season.Titles.Japanese,
- TitleSynonyms: strings.Join(season.Titles.Synonyms, ","),
- Synopsis: season.Synopsis,
- Type: string(season.Type),
- Source: season.Source,
- Airing: season.Airing,
- Status: season.Status,
- Duration: season.Duration,
- Season: season.Season,
- Year: season.Year,
- Current: season.Current,
- }
-
- // Add Images
- cachedSeason.Images = &entities.CachedAnimeImages{
- Small: season.Images.Small,
- Large: season.Images.Large,
- Original: season.Images.Original,
- }
-
- // Add Scores
- cachedSeason.Scores = &entities.CachedAnimeScores{
- Score: season.Scores.Score,
- ScoredBy: season.Scores.ScoredBy,
- Rank: season.Scores.Rank,
- Popularity: season.Scores.Popularity,
- Members: season.Scores.Members,
- Favorites: season.Scores.Favorites,
- }
-
- // Add AiringStatus
- if season.AiringStatus.From.Year > 0 || season.AiringStatus.From.String != "" {
- cachedSeason.AiringStatus = &entities.CachedAiringStatus{
- String: season.AiringStatus.String,
- }
-
- if season.AiringStatus.From.Year > 0 || season.AiringStatus.From.String != "" {
- cachedSeason.AiringStatus.From = &entities.CachedAiringStatusDates{
- Day: season.AiringStatus.From.Day,
- Month: season.AiringStatus.From.Month,
- Year: season.AiringStatus.From.Year,
- String: season.AiringStatus.From.String,
- }
- }
-
- if season.AiringStatus.To.Year > 0 || season.AiringStatus.To.String != "" {
- cachedSeason.AiringStatus.To = &entities.CachedAiringStatusDates{
- Day: season.AiringStatus.To.Day,
- Month: season.AiringStatus.To.Month,
- Year: season.AiringStatus.To.Year,
- String: season.AiringStatus.To.String,
- }
- }
- }
-
- cachedAnime.Seasons[i] = cachedSeason
- }
- }
-
- return cachedAnime
-}
-
-// hasLargeDataset checks if the anime has a large number of episodes that might exceed SQLite limits
-func hasLargeDataset(anime *entities.CachedAnime) bool {
- // SQLite has a limit of 999 variables per statement. Each episode has multiple fields,
- // so we need to be conservative. If we have more than 100 episodes, use batching.
- return len(anime.Episodes) > 100
-}
-
-// saveCachedAnimeWithBatching saves anime data using batching for large datasets (SQLite only)
-func saveCachedAnimeWithBatching(tx *gorm.DB, cachedAnime *entities.CachedAnime) error {
- // First save the main anime record without episodes and other large collections
- mainAnime := &entities.CachedAnime{
- MALID: cachedAnime.MALID,
- TitleRomaji: cachedAnime.TitleRomaji,
- TitleEnglish: cachedAnime.TitleEnglish,
- TitleJapanese: cachedAnime.TitleJapanese,
- TitleSynonyms: cachedAnime.TitleSynonyms,
- Synopsis: cachedAnime.Synopsis,
- Type: cachedAnime.Type,
- Source: cachedAnime.Source,
- Airing: cachedAnime.Airing,
- Status: cachedAnime.Status,
- Duration: cachedAnime.Duration,
- Color: cachedAnime.Color,
- Season: cachedAnime.Season,
- Year: cachedAnime.Year,
- SubbedCount: cachedAnime.SubbedCount,
- DubbedCount: cachedAnime.DubbedCount,
- TotalEpisodes: cachedAnime.TotalEpisodes,
- AiredEpisodes: cachedAnime.AiredEpisodes,
- LastUpdated: cachedAnime.LastUpdated,
-
- // Include small collections that won't cause variable limit issues
- Images: cachedAnime.Images,
- Logos: cachedAnime.Logos,
- Covers: cachedAnime.Covers,
- Scores: cachedAnime.Scores,
- AiringStatus: cachedAnime.AiringStatus,
- Broadcast: cachedAnime.Broadcast,
- NextAiringEpisode: cachedAnime.NextAiringEpisode,
- Genres: cachedAnime.Genres,
- Producers: cachedAnime.Producers,
- Studios: cachedAnime.Studios,
- Licensors: cachedAnime.Licensors,
- AiringSchedule: cachedAnime.AiringSchedule,
- }
-
- // Save main anime with small collections
- if err := tx.Create(mainAnime).Error; err != nil {
- return fmt.Errorf("failed to save main anime record: %w", err)
- }
-
- // Now handle large collections in batches
- animeID := mainAnime.ID
-
- // Save episodes in batches
- if len(cachedAnime.Episodes) > 0 {
- batchSize := 50
- logger.Log(fmt.Sprintf("Saving %d episodes in batches of %d for anime %d",
- len(cachedAnime.Episodes), batchSize, animeID), logger.LogOptions{
- Level: logger.Debug,
- Prefix: "AnimeCache",
- })
-
- for i := 0; i < len(cachedAnime.Episodes); i += batchSize {
- end := i + batchSize
- if end > len(cachedAnime.Episodes) {
- end = len(cachedAnime.Episodes)
- }
-
- batch := make([]entities.CachedAnimeSingleEpisode, end-i)
- copy(batch, cachedAnime.Episodes[i:end])
-
- // Set the anime ID for each episode in the batch
- for j := range batch {
- batch[j].AnimeID = animeID
- }
-
- if err := tx.Create(&batch).Error; err != nil {
- return fmt.Errorf("failed to save episodes batch %d-%d: %w", i, end-1, err)
- }
- }
- }
-
- // Save characters in batches (they can also be large with voice actors)
- if len(cachedAnime.Characters) > 0 {
- batchSize := 20 // Smaller batch for characters due to nested voice actors
-
- for i := 0; i < len(cachedAnime.Characters); i += batchSize {
- end := i + batchSize
- if end > len(cachedAnime.Characters) {
- end = len(cachedAnime.Characters)
- }
-
- batch := make([]entities.CachedAnimeCharacter, end-i)
- copy(batch, cachedAnime.Characters[i:end])
-
- // Set the anime ID for each character in the batch
- for j := range batch {
- batch[j].AnimeID = animeID
- }
-
- if err := tx.Create(&batch).Error; err != nil {
- return fmt.Errorf("failed to save characters batch %d-%d: %w", i, end-1, err)
- }
- }
- }
-
- // Save seasons in batches
- if len(cachedAnime.Seasons) > 0 {
- batchSize := 10 // Even smaller for seasons due to complex nested relationships
-
- for i := 0; i < len(cachedAnime.Seasons); i += batchSize {
- end := i + batchSize
- if end > len(cachedAnime.Seasons) {
- end = len(cachedAnime.Seasons)
- }
-
- batch := make([]entities.CachedAnimeSeason, end-i)
- copy(batch, cachedAnime.Seasons[i:end])
-
- // Set the parent anime ID for each season in the batch
- for j := range batch {
- batch[j].ParentAnimeID = animeID
- }
-
- if err := tx.Create(&batch).Error; err != nil {
- return fmt.Errorf("failed to save seasons batch %d-%d: %w", i, end-1, err)
- }
- }
- }
-
- return nil
+ return anime
}