aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2026-02-24 14:17:57 +0530
committerBobby <[email protected]>2026-02-24 14:17:57 +0530
commit569273e5549e56ddc8ed52a0022193cb254cd4b3 (patch)
treee67ef0860a5867ab01174cd8d1c036b03ada59c6
parentde0c8e5b052342bc7aaa3e8e3795ce432208af61 (diff)
downloadmetachan-569273e5549e56ddc8ed52a0022193cb254cd4b3.tar.xz
metachan-569273e5549e56ddc8ed52a0022193cb254cd4b3.zip
Add anime stub retrieval and enhance anime sync logic with force refresh capability
-rw-r--r--repositories/anime.go8
-rw-r--r--repositories/types.go6
-rw-r--r--services/anime.go47
-rw-r--r--tasks/anisync.task.go53
-rw-r--r--tasks/aniupdate.task.go2
-rw-r--r--tasks/tasks.go3
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"},
})