package op import ( "errors" "fmt" "time" "github.com/synctv-org/synctv/internal/db" "github.com/synctv-org/synctv/internal/model" "github.com/zijiren233/gencontainer/rwmap" rtmps "github.com/zijiren233/livelib/server" "gorm.io/gorm" ) type movies struct { roomID string room *Room cache rwmap.RWMap[string, *Movie] } func (m *movies) AddMovie(mo *model.Movie) error { mo.Position = uint(time.Now().UnixMilli()) movie := &Movie{ room: m.room, Movie: mo, } err := movie.Validate() if err != nil { return err } err = db.CreateMovie(mo) if err != nil { return err } old, ok := m.cache.Swap(mo.ID, movie) if ok { _ = old.Close() } return nil } func (m *movies) AddMovies(mos []*model.Movie) error { inited := make([]*Movie, 0, len(mos)) for _, mo := range mos { mo.Position = uint(time.Now().UnixMilli()) movie := &Movie{ room: m.room, Movie: mo, } err := movie.Validate() if err != nil { return err } inited = append(inited, movie) } err := db.CreateMovies(mos) if err != nil { return err } for _, mo := range inited { old, ok := m.cache.Swap(mo.Movie.ID, mo) if ok { _ = old.Close() } } return nil } func (m *movies) GetChannel(id string) (*rtmps.Channel, error) { if id == "" { return nil, errors.New("channel name is nil") } movie, err := m.GetMovieByID(id) if err != nil { return nil, err } return movie.Channel() } func (m *movies) Update(movieID string, movie *model.MovieBase) error { mv, err := db.GetMovieByID(m.roomID, movieID) if err != nil { return err } mv.MovieBase = *movie err = db.SaveMovie(mv) if err != nil { return err } mm, loaded := m.cache.LoadAndDelete(mv.ID) if loaded { _ = mm.Close() } return nil } func (m *movies) Clear() error { return m.DeleteMovieByParentID("") } func (m *movies) ClearCache() { m.cache.Range(func(key string, value *Movie) bool { m.cache.CompareAndDelete(key, value) value.Close() return true }) } func (m *movies) Close() error { m.ClearCache() return nil } func (m *movies) DeleteMovieByParentID(parentID string) error { err := db.DeleteMoviesByRoomIDAndParentID(m.roomID, parentID) if err != nil { return err } m.DeleteMovieAndChiledCache(parentID) return nil } func (m *movies) DeleteMovieByID(id string) error { err := db.DeleteMovieByID(m.roomID, id) if err != nil { return err } m.DeleteMovieAndChiledCache(id) return nil } func (m *movies) DeleteMovieAndChiledCache(id ...string) { idm := make(map[model.EmptyNullString]struct{}, len(id)) for _, id := range id { idm[model.EmptyNullString(id)] = struct{}{} } if _, ok := idm[model.EmptyNullString("")]; ok { m.ClearCache() return } m.deleteMovieAndChiledCache(idm) } func (m *movies) deleteMovieAndChiledCache(ids map[model.EmptyNullString]struct{}) { next := make(map[model.EmptyNullString]struct{}) m.cache.Range(func(key string, value *Movie) bool { if _, ok := ids[value.ParentID]; ok { if value.IsFolder { next[model.EmptyNullString(value.ID)] = struct{}{} } m.cache.CompareAndDelete(key, value) value.Close() } return true }) if len(next) > 0 { m.deleteMovieAndChiledCache(next) } } func (m *movies) DeleteMoviesByID(ids []string) error { err := db.DeleteMoviesByID(m.roomID, ids) if err != nil { return err } m.DeleteMovieAndChiledCache(ids...) return nil } func (m *movies) GetMovieByID(id string) (*Movie, error) { if id == "" { return nil, errors.New("movie id is nil") } mm, ok := m.cache.Load(id) if ok { return mm, nil } mv, err := db.GetMovieByID(m.roomID, id) if err != nil { return nil, err } mm, _ = m.cache.LoadOrStore(mv.ID, &Movie{ room: m.room, Movie: mv, }) return mm, nil } func (m *movies) SwapMoviePositions(id1, id2 string) error { return db.SwapMoviePositions(m.roomID, id1, id2) } func (m *movies) GetMoviesWithPage(keyword string, page, pageSize int, parentID string) ([]*model.Movie, int64, error) { scopes := []func(*gorm.DB) *gorm.DB{ db.WithParentMovieID(parentID), } if keyword != "" { scopes = append(scopes, db.WhereMovieNameLikeOrURLLike(keyword, keyword)) } count, err := db.GetMoviesCountByRoomID(m.roomID, append(scopes, db.Paginate(page, pageSize))...) if err != nil { return nil, 0, err } movies, err := db.GetMoviesByRoomID(m.roomID, scopes...) if err != nil { return nil, 0, err } return movies, count, nil } // IsParentOf check if parentID is the parent of id func (m *movies) IsParentOf(id, parentID string) (bool, error) { if parentID == "" { return id != "", nil } mv, err := m.GetMovieByID(parentID) if err != nil { return false, fmt.Errorf("get parent movie failed: %w", err) } if !mv.IsFolder { return false, nil } return m.isParentOf(id, parentID, true) } func (m *movies) IsParentFolder(id, parentID string) (bool, error) { if parentID == "" { return id != "", nil } mv, err := m.GetMovieByID(parentID) if err != nil { return false, fmt.Errorf("get parent movie failed: %w", err) } firstCheck := true if mv.IsFolder { firstCheck = false } else { parentID = mv.ParentID.String() } return m.isParentOf(id, parentID, firstCheck) } func (m *movies) isParentOf(id, parentID string, firstCheck bool) (bool, error) { mv, err := m.GetMovieByID(id) if err != nil { return false, err } if mv.ParentID == "" { return false, nil } if mv.ParentID == model.EmptyNullString(parentID) { return !firstCheck, nil } return m.isParentOf(string(mv.ParentID), parentID, false) }