From 569273e5549e56ddc8ed52a0022193cb254cd4b3 Mon Sep 17 00:00:00 2001 From: Bobby <30593201+luciferreeves@users.noreply.github.com> Date: Tue, 24 Feb 2026 14:17:57 +0530 Subject: Add anime stub retrieval and enhance anime sync logic with force refresh capability --- repositories/anime.go | 8 ++++++++ repositories/types.go | 6 ++++++ services/anime.go | 47 +++++++++++++++++++++++++++++++++++++++---- tasks/anisync.task.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ tasks/aniupdate.task.go | 2 +- tasks/tasks.go | 3 ++- 6 files changed, 113 insertions(+), 6 deletions(-) diff --git a/repositories/anime.go b/repositories/anime.go index b20a256..ac10189 100644 --- a/repositories/anime.go +++ b/repositories/anime.go @@ -206,6 +206,14 @@ func SaveEpisodeSkipTimes(episodeID string, skipTimes []entities.EpisodeSkipTime return nil } +func GetAllAnimeStubs() ([]animeStub, error) { + var stubs []animeStub + if err := DB.Model(&entities.Anime{}).Select("mal_id, updated_at").Scan(&stubs).Error; err != nil { + return nil, err + } + return stubs, nil +} + func GetAiringAnime() ([]entities.Anime, error) { var anime []entities.Anime diff --git a/repositories/types.go b/repositories/types.go index dcce328..e4e349e 100644 --- a/repositories/types.go +++ b/repositories/types.go @@ -2,6 +2,7 @@ package repositories import ( "metachan/database" + "time" "gorm.io/gorm" ) @@ -11,3 +12,8 @@ var DB *gorm.DB = database.DB type idType interface { ~int | ~string } + +type animeStub struct { + MALID int + UpdatedAt time.Time +} diff --git a/services/anime.go b/services/anime.go index eba303b..6efd81e 100644 --- a/services/anime.go +++ b/services/anime.go @@ -15,6 +15,7 @@ import ( "metachan/utils/api/tvdb" "metachan/utils/logger" "strings" + "time" ) func GetAnime(mapping *entities.Mapping) (*entities.Anime, error) { @@ -26,13 +27,42 @@ func GetAnime(mapping *entities.Mapping) (*entities.Anime, error) { malID := mapping.MAL logger.Infof("AnimeService", "Fetching anime data for MAL ID: %d", malID) - var anime *entities.Anime existingAnime, err := repositories.GetAnime(enums.MAL, malID) if err == nil { - logger.Infof("AnimeService", "Found existing anime in database, will update with fresh data") - anime = &existingAnime + if time.Since(existingAnime.UpdatedAt) < 7*24*time.Hour { + logger.Infof("AnimeService", "Returning cached anime (MAL ID: %d, age: %v)", malID, time.Since(existingAnime.UpdatedAt).Round(time.Second)) + return &existingAnime, nil + } + logger.Infof("AnimeService", "Cached anime is stale, refreshing (MAL ID: %d)", malID) + return fetchAnime(mapping, &existingAnime) + } + + logger.Infof("AnimeService", "Anime not found in database, creating new") + return fetchAnime(mapping, nil) +} + +func ForceRefreshAnime(mapping *entities.Mapping) (*entities.Anime, error) { + if mapping == nil { + logger.Errorf("AnimeService", "Mapping is nil") + return nil, fmt.Errorf("mapping is nil") + } + + logger.Infof("AnimeService", "Force refreshing anime data for MAL ID: %d", mapping.MAL) + + existingAnime, err := repositories.GetAnime(enums.MAL, mapping.MAL) + if err == nil { + return fetchAnime(mapping, &existingAnime) + } + return fetchAnime(mapping, nil) +} + +func fetchAnime(mapping *entities.Mapping, existing *entities.Anime) (*entities.Anime, error) { + malID := mapping.MAL + + var anime *entities.Anime + if existing != nil { + anime = existing } else { - logger.Infof("AnimeService", "Anime not found in database, creating new") anime = &entities.Anime{ MALID: malID, Mapping: mapping, @@ -56,6 +86,15 @@ func GetAnime(mapping *entities.Mapping) (*entities.Anime, error) { logger.Warnf("AnimeService", "Failed to fetch characters from Jikan: %v", err) } + // Reset all slice fields so re-fetching doesn't double-append onto existing DB data + anime.Episodes = nil + anime.Characters = nil + anime.Genres = nil + anime.Producers = nil + anime.Studios = nil + anime.Licensors = nil + anime.Schedule = nil + applyJikanData(anime, jikanAnime, jikanEpisodes, jikanCharacters) if mapping.Anilist > 0 { diff --git a/tasks/anisync.task.go b/tasks/anisync.task.go index 0a346ac..9aa64f0 100644 --- a/tasks/anisync.task.go +++ b/tasks/anisync.task.go @@ -1,6 +1,7 @@ package tasks import ( + "metachan/entities" "metachan/enums" "metachan/repositories" "metachan/services" @@ -67,3 +68,55 @@ func AniSync() error { return nil } + +// ResumeAnimeSync is called on startup to resume any interrupted sync and refresh stale entries. +func ResumeAnimeSync() { + go func() { + mappings, err := repositories.GetAllMappings() + if err != nil { + logger.Errorf("AniSync", "Resume: failed to fetch mappings: %v", err) + return + } + + stubs, err := repositories.GetAllAnimeStubs() + if err != nil { + logger.Errorf("AniSync", "Resume: failed to fetch anime stubs: %v", err) + return + } + + sevenDaysAgo := time.Now().Add(-7 * 24 * time.Hour) + updatedAt := make(map[int]time.Time, len(stubs)) + for _, s := range stubs { + updatedAt[s.MALID] = s.UpdatedAt + } + + var toProcess []entities.Mapping + for _, m := range mappings { + if m.MAL == 0 { + continue + } + t, exists := updatedAt[m.MAL] + if !exists || t.Before(sevenDaysAgo) { + toProcess = append(toProcess, m) + } + } + + if len(toProcess) == 0 { + return + } + + logger.Infof("AniSync", "Resume: %d anime to sync (missing or stale)", len(toProcess)) + startTime := time.Now() + + for i, m := range toProcess { + progress, eta := calculateProgress(i+1, len(toProcess), startTime) + logger.Infof("AniSync", "[Resume %d/%d] MAL ID %d - %.1f%% | ETA: %v", i+1, len(toProcess), m.MAL, progress, eta) + + if _, err := services.GetAnime(&m); err != nil { + logger.Warnf("AniSync", "Resume: failed to sync MAL ID %d: %v", m.MAL, err) + } + } + + logger.Successf("AniSync", "Resume complete: synced %d anime", len(toProcess)) + }() +} diff --git a/tasks/aniupdate.task.go b/tasks/aniupdate.task.go index 864ea05..9952d23 100644 --- a/tasks/aniupdate.task.go +++ b/tasks/aniupdate.task.go @@ -138,7 +138,7 @@ func updateAnime(series entities.Anime, reason string) { return } - updatedAnime, err := services.GetAnime(&mapping) + updatedAnime, err := services.ForceRefreshAnime(&mapping) if err != nil { logger.Errorf("AnimeUpdate", "Error getting updated anime data for %s (MAL ID: %d): %v", title, series.MALID, err) return diff --git a/tasks/tasks.go b/tasks/tasks.go index 8eb22f4..a60197a 100644 --- a/tasks/tasks.go +++ b/tasks/tasks.go @@ -58,8 +58,9 @@ func init() { if config.Sync.AniSync { err = GlobalTaskManager.RegisterTask(types.Task{ Name: "AnimeSync", - Interval: 0, // Manual-only - waits for AnimeFetch dependency + Interval: 0, Execute: AniSync, + OnResume: ResumeAnimeSync, Dependencies: []string{"AnimeFetch"}, }) -- cgit v1.2.3