Compare commits

...

9 Commits

Author SHA1 Message Date
Simon Ding
12c3b0c69b add log 2025-11-12 17:10:58 +08:00
Simon Ding
4825cda458 batch delete 2025-11-12 17:08:33 +08:00
Simon Ding
6a12c380f1 housekeeping run at 2:00am 2025-11-12 15:01:38 +08:00
Simon Ding
d8944168d2 add log 2025-11-12 14:53:36 +08:00
Simon Ding
0a48657999 add log 2025-11-12 14:09:33 +08:00
Simon Ding
717b098d2f feat: run housekeeping once at start 2025-11-12 14:01:56 +08:00
Simon Ding
1f6b704405 add db housekeeping 2025-11-12 14:00:38 +08:00
Simon Ding
e2ec07aaa2 Merge branch 'main' of github.com:simon-ding/polaris 2025-11-12 13:43:08 +08:00
Simon Ding
3aa72cd6f8 feat: cache downloaded status data every 10min 2025-11-12 13:42:51 +08:00
6 changed files with 130 additions and 38 deletions

View File

@@ -31,7 +31,6 @@ import (
"modernc.org/sqlite"
)
// https://github.com/ent/ent/discussions/1667#discussioncomment-1132296
type sqliteDriver struct {
*sqlite.Driver
@@ -232,6 +231,15 @@ func (c *client) GetMediaWatchlist(mediaType media.MediaType) []*ent.Media {
return list
}
func (c *client) GetAllEpisodes() (ent.Episodes, error) {
ep, err := c.ent.Episode.Query().All(context.TODO())
return ep, err
}
func (c *client) DeleteEpisode(ids ...int) error {
_, err := c.ent.Episode.Delete().Where(episode.IDIn(ids...)).Exec(context.TODO())
return err
}
func (c *client) GetEpisode(seriesId, seasonNum, episodeNum int) (*ent.Episode, error) {
return c.ent.Episode.Query().Where(episode.MediaID(seriesId), episode.SeasonNumber(seasonNum),
episode.EpisodeNumber(episodeNum)).First(context.TODO())

View File

@@ -59,7 +59,6 @@ type Settings interface {
GetAcceptedSubtitleFormats() ([]string, error)
SetAcceptedSubtitleFormats(key string, v []string) error
GetTmdbApiKey() string
}
type MediaApis interface {
@@ -75,6 +74,8 @@ type MediaApis interface {
}
type EpisodeApis interface {
GetAllEpisodes() (ent.Episodes, error)
DeleteEpisode(ids ...int) error
GetEpisode(seriesId, seasonNum, episodeNum int) (*ent.Episode, error)
GetEpisodeByID(epID int) (*ent.Episode, error)
UpdateEpiode(episodeId int, name, overview string) error
@@ -89,7 +90,6 @@ type EpisodeApis interface {
UpdateEpisodeTargetFile(id int, filename string) error
GetSeasonEpisodes(mediaId, seasonNum int) ([]*ent.Episode, error)
CleanAllDanglingEpisodes() error
}
type IndexerApis interface {
@@ -97,7 +97,6 @@ type IndexerApis interface {
DeleteIndexer(id int)
GetIndexer(id int) (*ent.Indexers, error)
GetAllIndexers() []*ent.Indexers
}
type HistoryApis interface {
@@ -108,5 +107,5 @@ type HistoryApis interface {
GetHistories() ent.Histories
DeleteHistory(id int) error
GetDownloadHistory(mediaID int) ([]*ent.History, error)
GetMovieDummyEpisode(movieId int) (*ent.Episode, error)
}
GetMovieDummyEpisode(movieId int) (*ent.Episode, error)
}

View File

@@ -1,26 +1,65 @@
package engine
import "polaris/log"
import (
"polaris/ent/media"
"polaris/log"
)
func (c *Engine) housekeeping() error {
func (c *Engine) housekeeping() (err error) {
log.Infof("start housekeeping tasks...")
defer func() {
log.Infof("housekeeping tasks completed. err: %v", err)
}()
if err := c.checkDbScraps(); err != nil {
return err
}
if err := c.checkImageFilesInterity(); err != nil {
return err
}
return nil
}
func (c *Engine) checkDbScraps() error {
//TODO: remove episodes that are not associated with any series
tvs := c.db.GetMediaWatchlist(media.MediaTypeTv)
movies := c.db.GetMediaWatchlist(media.MediaTypeMovie)
validMediaIDs := make(map[int]bool, len(tvs)+len(movies))
for _, tv := range tvs {
validMediaIDs[tv.ID] = true
}
for _, movie := range movies {
validMediaIDs[movie.ID] = true
}
allEpisodes, err := c.db.GetAllEpisodes()
if err != nil {
log.Debugf("get all episodes error: %v", err)
return err
}
log.Infof("check db scraps, total episodes: %v, total media: %v", len(allEpisodes), len(validMediaIDs))
toDeleteIds := make([]int, 0)
for _, ep := range allEpisodes {
if _, ok := validMediaIDs[ep.MediaID]; !ok {
//log.Infof("remove scrap episode record: %v S%vE%v", ep.MediaID, ep.SeasonNumber, ep.EpisodeNumber)
toDeleteIds = append(toDeleteIds, ep.ID)
}
}
log.Infof("%v scrap episode records will be removed...", len(toDeleteIds))
if err := c.db.DeleteEpisode(toDeleteIds...); err != nil {
log.Errorf("delete scrap episode records error: %v", err)
}
return nil
}
func (c *Engine) checkImageFilesInterity() error {
//TODO: download missing image files, remove unused image files
return nil
}
}

View File

@@ -42,7 +42,8 @@ func (c *Engine) addSysCron() {
})
c.registerCronJob("check_series_new_release", "0 0 */12 * * *", c.checkAllSeriesNewSeason)
c.registerCronJob("update_import_lists", "0 */20 * * * *", c.periodicallyUpdateImportlist)
c.registerCronJob("housekeeping", "0 0 * * * *", c.housekeeping)
c.registerCronJob("housekeeping", "0 0 2 * * *", c.housekeeping)
go c.housekeeping() //run once on startup
c.schedulers.Range(func(key string, value scheduler) bool {
log.Debugf("add cron job: %v", key)

View File

@@ -26,10 +26,10 @@ import (
func NewServer(db db.Database) *Server {
s := &Server{
db: db,
srv: &http.Server{},
srv: &http.Server{},
language: db.GetLanguage(),
monitorNumCache: cache.NewCache[int, int](10 * time.Minute),
downloadNumCache: cache.NewCache[int, int](10 * time.Minute),
monitorNumCache: cache.NewCache[int, int](30 * time.Minute),
downloadNumCache: cache.NewCache[int, int](30 * time.Minute),
}
s.core = engine.NewEngine(db, s.language)
s.setupRoutes()
@@ -58,7 +58,7 @@ func (s *Server) setupRoutes() {
} else {
log.Warnf("serve web static files error: %v", err)
}
//s.r.Use(ginzap.Ginzap(log.Logger().Desugar(), time.RFC3339, false))
r.Use(ginzap.RecoveryWithZap(log.Logger().Desugar(), true))
@@ -179,6 +179,14 @@ func (s *Server) Start(addr string) (int, error) {
log.Infof("----------- Polaris Server Successfully Started on Port %d------------", p)
ticker := time.NewTicker(10 * time.Minute)
go func() {
for {
s.cacheDownloadedStatus()
<-ticker.C
}
}()
return p, nil
}

View File

@@ -89,6 +89,60 @@ type MediaWithStatus struct {
DownloadedNum int `json:"downloaded_num"`
}
func (s *Server) cacheDownloadedStatus() {
log.Info("cache watchlist downloaded/monitored status")
list := s.db.GetMediaWatchlist(media.MediaTypeTv)
for _, item := range list {
var ms = MediaWithStatus{
Media: item,
MonitoredNum: 0,
DownloadedNum: 0,
}
mon, ok1 := s.monitorNumCache.Get(item.ID)
dow, ok2 := s.downloadNumCache.Get(item.ID)
if ok1 && ok2 {
ms.MonitoredNum = mon
ms.DownloadedNum = dow
} else {
details, err := s.db.GetMediaDetails(item.ID)
if err != nil {
log.Warnf("get media details: %v", err)
continue
}
for _, ep := range details.Episodes {
if ep.Monitored {
ms.MonitoredNum++
if ep.Status == episode.StatusDownloaded {
ms.DownloadedNum++
}
}
}
s.monitorNumCache.Set(item.ID, ms.MonitoredNum)
s.downloadNumCache.Set(item.ID, ms.DownloadedNum)
}
}
list = s.db.GetMediaWatchlist(media.MediaTypeMovie)
for _, item := range list {
_, ok2 := s.downloadNumCache.Get(item.ID)
if ok2 {
continue
}
dummyEp, err := s.db.GetMovieDummyEpisode(item.ID)
if err != nil {
log.Errorf("get dummy episode: %v", err)
} else {
if dummyEp.Status == episode.StatusDownloaded {
s.downloadNumCache.Set(item.ID, 1)
}
}
}
}
//missing: episode aired missing
//downloaded: all monitored episode downloaded
//monitoring: episode aired downloaded, but still has not aired episode
@@ -109,20 +163,7 @@ func (s *Server) GetTvWatchlist(c *gin.Context) (interface{}, error) {
ms.MonitoredNum = mon
ms.DownloadedNum = dow
} else {
details, err := s.db.GetMediaDetails(item.ID)
if err != nil {
return nil, errors.Wrap(err, "get details")
}
for _, ep := range details.Episodes {
if ep.Monitored {
ms.MonitoredNum++
if ep.Status == episode.StatusDownloaded {
ms.DownloadedNum++
}
}
}
s.monitorNumCache.Set(item.ID, ms.MonitoredNum)
s.downloadNumCache.Set(item.ID, ms.DownloadedNum)
continue
}
res[i] = ms
@@ -139,14 +180,10 @@ func (s *Server) GetMovieWatchlist(c *gin.Context) (interface{}, error) {
MonitoredNum: 1,
DownloadedNum: 0,
}
dummyEp, err := s.db.GetMovieDummyEpisode(item.ID)
if err != nil {
log.Errorf("get dummy episode: %v", err)
} else {
if dummyEp.Status == episode.StatusDownloaded {
ms.DownloadedNum++
}
}
dow, ok2 := s.downloadNumCache.Get(item.ID)
if ok2 {
ms.DownloadedNum = dow
}
res[i] = ms
}
return res, nil