From 854e78c1f4ccf071cd120f57f70fa396afc759c6 Mon Sep 17 00:00:00 2001 From: zijiren233 Date: Sat, 21 Oct 2023 17:12:26 +0800 Subject: [PATCH] Feat: lazy init movies --- internal/db/movie.go | 9 +- internal/op/movie.go | 298 +++++++++++++++++++-------------------- internal/op/movies.go | 220 +++++++++++++++++++++++++++++ internal/op/op.go | 4 - internal/op/room.go | 284 ++++--------------------------------- internal/op/rooms.go | 14 +- server/handlers/movie.go | 29 +--- utils/utils.go | 22 +-- 8 files changed, 416 insertions(+), 464 deletions(-) create mode 100644 internal/op/movies.go diff --git a/internal/db/movie.go b/internal/db/movie.go index 019bb16..cd64e4b 100644 --- a/internal/db/movie.go +++ b/internal/db/movie.go @@ -13,13 +13,10 @@ func CreateMovie(movie *model.Movie) error { return db.Create(movie).Error } -func GetAllMoviesByRoomID(roomID uint) ([]*model.Movie, error) { +func GetAllMoviesByRoomID(roomID uint) []*model.Movie { movies := []*model.Movie{} - err := db.Where("room_id = ?", roomID).Order("position ASC").Find(&movies).Error - if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { - return movies, errors.New("room not found") - } - return movies, err + db.Where("room_id = ?", roomID).Order("position ASC").Find(&movies) + return movies } func DeleteMovieByID(roomID, id uint) error { diff --git a/internal/op/movie.go b/internal/op/movie.go index 218906c..5dad55b 100644 --- a/internal/op/movie.go +++ b/internal/op/movie.go @@ -2,180 +2,166 @@ package op import ( "errors" + "net/url" + "sync" "time" - "github.com/bluele/gcache" - "github.com/synctv-org/synctv/internal/db" + "github.com/go-resty/resty/v2" + "github.com/google/uuid" + "github.com/synctv-org/synctv/internal/conf" "github.com/synctv-org/synctv/internal/model" - "github.com/zijiren233/gencontainer/dllist" + "github.com/synctv-org/synctv/utils" + "github.com/zijiren233/livelib/av" + "github.com/zijiren233/livelib/container/flv" + rtmpProto "github.com/zijiren233/livelib/protocol/rtmp" + "github.com/zijiren233/livelib/protocol/rtmp/core" + rtmps "github.com/zijiren233/livelib/server" ) -var movieCache gcache.Cache - -func GetAllMoviesByRoomID(roomID uint) (*dllist.Dllist[*model.Movie], error) { - i, err := movieCache.Get(roomID) - if err == nil { - return i.(*dllist.Dllist[*model.Movie]), nil - } - m, err := db.GetAllMoviesByRoomID(roomID) - if err != nil { - return nil, err - } - d := dllist.New[*model.Movie]() - for i := range m { - d.PushBack(m[i]) - } - return d, movieCache.SetWithExpire(roomID, d, time.Hour) +type movie struct { + *model.Movie + lock sync.RWMutex + channel *rtmps.Channel } -func GetMoviesByRoomIDWithPage(roomID uint, page, max int) ([]*model.Movie, error) { - ms, err := GetAllMoviesByRoomID(roomID) - if err != nil { - return nil, err - } - start := (page - 1) * max - if start >= ms.Len() { - start = ms.Len() - } - end := start + max - if end > ms.Len() { - end = ms.Len() - } - var m []*model.Movie = make([]*model.Movie, 0, end-start) - idx := 0 - for i := ms.Front(); i != nil; i = i.Next() { - if idx >= start && idx < end { - m = append(m, i.Value) - } - idx++ - } - return m, nil +func (m *movie) Channel() (*rtmps.Channel, error) { + m.lock.Lock() + defer m.lock.Unlock() + return m.channel, m.init() } -func GetMovieByID(roomID, id uint) (*model.Movie, error) { - ms, err := GetAllMoviesByRoomID(roomID) - if err != nil { - return nil, err - } - for i := ms.Front(); i != nil; i = i.Next() { - if i.Value.ID == id { - return i.Value, nil +func (m *movie) init() (err error) { + switch { + case m.RtmpSource && m.Proxy: + return errors.New("rtmp source and proxy can't be true at the same time") + case m.Live && m.RtmpSource: + if !conf.Conf.Rtmp.Enable { + return errors.New("rtmp is not enabled") } - } - return nil, errors.New("movie not found") -} - -func GetMoviesCountByRoomID(roomID uint) (int, error) { - ms, err := GetAllMoviesByRoomID(roomID) - if err != nil { - return 0, err - } - return ms.Len(), nil -} - -func DeleteMovieByID(roomID, id uint) error { - ms, err := GetAllMoviesByRoomID(roomID) - if err != nil { - return err - } - for i := ms.Front(); i != nil; i = i.Next() { - if i.Value.ID == id { - ms.Remove(i) - return db.DeleteMovieByID(roomID, id) + if m.PullKey == "" { + m.PullKey = uuid.NewString() } - } - return errors.New("movie not found") -} - -func UpdateMovie(movie *model.Movie) error { - err := db.UpdateMovie(movie) - if err != nil { - return err - } - m, err := GetMovieByID(movie.RoomID, movie.ID) - if err != nil { - return err - } - *m = *movie - return nil -} - -func SaveMovie(movie *model.Movie) error { - err := db.SaveMovie(movie) - if err != nil { - return err - } - m, err := GetMovieByID(movie.RoomID, movie.ID) - if err != nil { - return err - } - *m = *movie - return nil -} - -func DeleteMoviesByRoomID(roomID uint) error { - movieCache.Remove(roomID) - return db.DeleteMoviesByRoomID(roomID) -} - -func LoadAndDeleteMovieByID(roomID, id uint) (*model.Movie, error) { - ms, err := GetAllMoviesByRoomID(roomID) - if err != nil { - return nil, err - } - for i := ms.Front(); i != nil; i = i.Next() { - if i.Value.ID == id { - ms.Remove(i) - return db.LoadAndDeleteMovieByID(roomID, id) + if m.channel == nil { + m.channel = rtmps.NewChannel() + m.channel.InitHlsPlayer() + } + case m.Live && m.Proxy: + if !conf.Conf.Proxy.LiveProxy { + return errors.New("live proxy is not enabled") + } + u, err := url.Parse(m.Url) + if err != nil { + return err + } + if utils.IsLocalIP(u.Host) { + return errors.New("local ip is not allowed") + } + switch u.Scheme { + case "rtmp": + m.PullKey = uuid.NewMD5(uuid.NameSpaceURL, []byte(m.Url)).String() + if m.channel == nil { + m.channel = rtmps.NewChannel() + m.channel.InitHlsPlayer() + go func() { + for { + if m.channel.Closed() { + return + } + cli := core.NewConnClient() + if err = cli.Start(m.Url, av.PLAY); err != nil { + cli.Close() + time.Sleep(time.Second) + continue + } + if err := m.channel.PushStart(rtmpProto.NewReader(cli)); err != nil { + cli.Close() + time.Sleep(time.Second) + } + } + }() + } + case "http", "https": + if m.Type != "flv" { + return errors.New("only flv is supported") + } + m.PullKey = uuid.NewMD5(uuid.NameSpaceURL, []byte(m.Url)).String() + if m.channel == nil { + m.channel = rtmps.NewChannel() + m.channel.InitHlsPlayer() + go func() { + for { + if m.channel.Closed() { + return + } + r := resty.New().R() + for k, v := range m.Headers { + r.SetHeader(k, v) + } + // r.SetHeader("User-Agent", UserAgent) + resp, err := r.Get(m.Url) + if err != nil { + time.Sleep(time.Second) + continue + } + if err := m.channel.PushStart(flv.NewReader(resp.RawBody())); err != nil { + time.Sleep(time.Second) + } + resp.RawBody().Close() + } + }() + } + default: + return errors.New("unsupported scheme") + } + case !m.Live && m.RtmpSource: + return errors.New("rtmp source can't be true when movie is not live") + case !m.Live && m.Proxy: + if !conf.Conf.Proxy.MovieProxy { + return errors.New("movie proxy is not enabled") + } + u, err := url.Parse(m.Url) + if err != nil { + return err + } + if utils.IsLocalIP(u.Host) { + return errors.New("local ip is not allowed") + } + if u.Scheme != "http" && u.Scheme != "https" { + return errors.New("unsupported scheme") } + m.PullKey = uuid.NewMD5(uuid.NameSpaceURL, []byte(m.Url)).String() + case !m.Live && !m.Proxy, m.Live && !m.Proxy && !m.RtmpSource: + u, err := url.Parse(m.Url) + if err != nil { + return err + } + if u.Scheme != "http" && u.Scheme != "https" { + return errors.New("unsupported scheme") + } + m.PullKey = "" + default: + return errors.New("unknown error") } - return nil, errors.New("movie not found") + return nil } -// data race -func CreateMovie(movie *model.Movie) error { - ms, err := GetAllMoviesByRoomID(movie.RoomID) - if err != nil { - return err - } - err = db.CreateMovie(movie) - if err != nil { - return err - } - ms.PushBack(movie) - return nil +func (m *movie) Terminate() { + m.lock.Lock() + defer m.lock.Unlock() + m.terminate() } -func GetMovieWithPullKey(roomID uint, pullKey string) (*model.Movie, error) { - ms, err := GetAllMoviesByRoomID(roomID) - if err != nil { - return nil, err +func (m *movie) terminate() { + if m.channel != nil { + m.channel.Close() + m.channel = nil } - for i := ms.Front(); i != nil; i = i.Next() { - if i.Value.PullKey == pullKey { - return i.Value, nil - } - } - return nil, errors.New("movie not found") } -func SwapMoviePositions(roomID uint, movie1ID uint, movie2ID uint) error { - ms, err := GetAllMoviesByRoomID(roomID) - if err != nil { - return err - } - var m1, m2 *model.Movie - for i := ms.Front(); i != nil; i = i.Next() { - if i.Value.ID == movie1ID { - m1 = i.Value - } - if i.Value.ID == movie2ID { - m2 = i.Value - } - } - if m1 == nil || m2 == nil { - return errors.New("movie not found") - } - m1.Position, m2.Position = m2.Position, m1.Position - return db.SwapMoviePositions(roomID, movie1ID, movie2ID) +func (m *movie) Update(movie model.BaseMovieInfo) error { + m.lock.Lock() + defer m.lock.Unlock() + m.terminate() + m.Movie.BaseMovieInfo = movie + return m.init() } diff --git a/internal/op/movies.go b/internal/op/movies.go new file mode 100644 index 0000000..9d8f5be --- /dev/null +++ b/internal/op/movies.go @@ -0,0 +1,220 @@ +package op + +import ( + "errors" + "sync" + "time" + + "github.com/synctv-org/synctv/internal/db" + "github.com/synctv-org/synctv/internal/model" + "github.com/synctv-org/synctv/utils" + "github.com/zijiren233/gencontainer/dllist" + rtmps "github.com/zijiren233/livelib/server" +) + +type movies struct { + roomID uint + lock sync.RWMutex + list dllist.Dllist[*movie] + once sync.Once +} + +func (m *movies) init() { + m.once.Do(func() { + for _, m2 := range db.GetAllMoviesByRoomID(m.roomID) { + m.list.PushBack(&movie{ + Movie: m2, + }) + } + }) +} + +func (m *movies) Len() int { + m.lock.RLock() + defer m.lock.RUnlock() + m.init() + return m.list.Len() +} + +func (m *movies) Add(mo *model.Movie) error { + m.lock.Lock() + defer m.lock.Unlock() + m.init() + mo.Position = uint(time.Now().UnixMilli()) + movie := &movie{ + Movie: mo, + } + + // You need to init to get the pullKey first, and then create it into the database. + err := movie.init() + if err != nil { + return err + } + + err = db.CreateMovie(mo) + if err != nil { + movie.terminate() + return err + } + + m.list.PushBack(movie) + return nil +} + +func (m *movies) GetChannel(channelName string) (*rtmps.Channel, error) { + if channelName == "" { + return nil, errors.New("channel name is nil") + } + m.lock.RLock() + defer m.lock.RUnlock() + m.init() + for e := m.list.Front(); e != nil; e = e.Next() { + if e.Value.PullKey == channelName { + return e.Value.Channel() + } + } + return nil, errors.New("channel not found") +} + +func (m *movies) Update(movieId uint, movie model.BaseMovieInfo) error { + m.lock.Lock() + defer m.lock.Unlock() + m.init() + for e := m.list.Front(); e != nil; e = e.Next() { + if e.Value.ID == movieId { + err := e.Value.Update(movie) + if err != nil { + return err + } + return db.SaveMovie(e.Value.Movie) + } + } + return nil +} + +func (m *movies) Clear() error { + m.lock.Lock() + defer m.lock.Unlock() + err := db.DeleteMoviesByRoomID(m.roomID) + if err != nil { + return err + } + for e := m.list.Front(); e != nil; e = e.Next() { + e.Value.Terminate() + } + m.list.Clear() + return nil +} + +func (m *movies) Close() error { + m.lock.Lock() + defer m.lock.Unlock() + for e := m.list.Front(); e != nil; e = e.Next() { + e.Value.Terminate() + } + m.list.Clear() + return nil +} + +func (m *movies) DeleteMovieByID(id uint) error { + m.lock.Lock() + defer m.lock.Unlock() + m.init() + + err := db.DeleteMovieByID(m.roomID, id) + if err != nil { + return err + } + + for e := m.list.Front(); e != nil; e = e.Next() { + if e.Value.ID == id { + m.list.Remove(e).Terminate() + return nil + } + } + return errors.New("movie not found") +} + +func (m *movies) GetMovieByID(id uint) (*movie, error) { + m.lock.RLock() + defer m.lock.RUnlock() + return m.getMovieByID(id) +} + +func (m *movies) getMovieByID(id uint) (*movie, error) { + m.init() + for e := m.list.Front(); e != nil; e = e.Next() { + if e.Value.ID == id { + return e.Value, nil + } + } + return nil, errors.New("movie not found") +} + +func (m *movies) getMovieElementByID(id uint) (*dllist.Element[*movie], error) { + m.init() + for e := m.list.Front(); e != nil; e = e.Next() { + if e.Value.ID == id { + return e, nil + } + } + return nil, errors.New("movie not found") +} + +func (m *movies) GetMovieWithPullKey(pullKey string) (*movie, error) { + m.lock.RLock() + defer m.lock.RUnlock() + m.init() + + for e := m.list.Front(); e != nil; e = e.Next() { + if e.Value.PullKey == pullKey { + return e.Value, nil + } + } + return nil, errors.New("movie not found") +} + +func (m *movies) SwapMoviePositions(id1, id2 uint) error { + m.lock.Lock() + defer m.lock.Unlock() + m.init() + + err := db.SwapMoviePositions(m.roomID, id1, id2) + if err != nil { + return err + } + + movie1, err := m.getMovieElementByID(id1) + if err != nil { + return err + } + + movie2, err := m.getMovieElementByID(id2) + if err != nil { + return err + } + + movie1.Value.Position, movie2.Value.Position = movie2.Value.Position, movie1.Value.Position + + m.list.Swap(movie1, movie2) + return nil +} + +func (m *movies) GetMoviesWithPage(page, pageSize int) []*movie { + m.lock.RLock() + defer m.lock.RUnlock() + m.init() + + start, end := utils.GetPageItemsRange(m.list.Len(), page, pageSize) + ms := make([]*movie, 0, end-start) + i := 0 + for e := m.list.Front(); e != nil; e = e.Next() { + if i >= start && i < end { + ms = append(ms, e.Value) + } else if i >= end { + return ms + } + i++ + } + return ms +} diff --git a/internal/op/op.go b/internal/op/op.go index 0f349df..8a8f93c 100644 --- a/internal/op/op.go +++ b/internal/op/op.go @@ -9,9 +9,5 @@ func Init(size int) error { LRU(). Build() - movieCache = gcache.New(size). - LRU(). - Build() - return nil } diff --git a/internal/op/room.go b/internal/op/room.go index e85be2b..a86c381 100644 --- a/internal/op/room.go +++ b/internal/op/room.go @@ -3,23 +3,12 @@ package op import ( "errors" "hash/crc32" - "net/url" "sync/atomic" - "time" - "github.com/go-resty/resty/v2" - "github.com/google/uuid" "github.com/gorilla/websocket" - log "github.com/sirupsen/logrus" - "github.com/synctv-org/synctv/internal/conf" "github.com/synctv-org/synctv/internal/db" "github.com/synctv-org/synctv/internal/model" "github.com/synctv-org/synctv/utils" - "github.com/zijiren233/gencontainer/rwmap" - "github.com/zijiren233/livelib/av" - "github.com/zijiren233/livelib/container/flv" - rtmpProto "github.com/zijiren233/livelib/protocol/rtmp" - "github.com/zijiren233/livelib/protocol/rtmp/core" rtmps "github.com/zijiren233/livelib/server" "github.com/zijiren233/stream" "golang.org/x/crypto/bcrypt" @@ -31,28 +20,13 @@ type Room struct { current *current initOnce utils.Once hub *Hub - - channles rwmap.RWMap[string, *rtmps.Channel] + movies movies } -func (r *Room) LazyInit() (err error) { +func (r *Room) lazyInitHub() { r.initOnce.Do(func() { r.hub = newHub(r.ID) - - var ms []*model.Movie - ms, err = r.GetAllMovies() - if err != nil { - log.Errorf("failed to get movies: %s", err.Error()) - return - } - for _, m := range ms { - if err = r.initMovie(m); err != nil { - log.Errorf("lazy init room %d movie %d failed: %s", r.ID, m.ID, err.Error()) - DeleteMovieByID(r.ID, m.ID) - } - } }) - return } func (r *Room) ClientNum() int64 { @@ -70,26 +44,13 @@ func (r *Room) Broadcast(data Message, conf ...BroadcastConf) error { } func (r *Room) GetChannel(channelName string) (*rtmps.Channel, error) { - err := r.LazyInit() - if err != nil { - return nil, err - } - - c, ok := r.channles.Load(channelName) - if !ok { - return nil, errors.New("channel not found") - } - - return c, nil + return r.movies.GetChannel(channelName) } func (r *Room) close() { if r.initOnce.Done() { r.hub.Close() - r.channles.Range(func(_ string, c *rtmps.Channel) bool { - c.Close() - return true - }) + r.movies.Close() } } @@ -102,180 +63,12 @@ func (r *Room) CheckVersion(version uint32) bool { } func (r *Room) UpdateMovie(movieId uint, movie model.BaseMovieInfo) error { - err := r.LazyInit() - if err != nil { - return err - } - - m, err := GetMovieByID(r.ID, movieId) - if err != nil { - return err - } - - err = r.terminateMovie(m) - if err != nil { - return err - } - - m.MovieInfo.BaseMovieInfo = movie - - err = r.initMovie(m) - if err != nil { - return err - } - - return SaveMovie(m) -} - -func (r *Room) terminateMovie(movie *model.Movie) error { - switch { - case movie.Live && movie.RtmpSource, movie.Live && movie.Proxy: - c, loaded := r.channles.LoadAndDelete(movie.PullKey) - if loaded { - return c.Close() - } - } - return nil -} - -func (r *Room) initMovie(movie *model.Movie) error { - switch { - case movie.RtmpSource && movie.Proxy: - return errors.New("rtmp source and proxy can't be true at the same time") - case movie.Live && movie.RtmpSource: - if !conf.Conf.Rtmp.Enable { - return errors.New("rtmp is not enabled") - } - if movie.PullKey == "" { - movie.PullKey = uuid.NewString() - } - c, loaded := r.channles.LoadOrStore(movie.PullKey, rtmps.NewChannel()) - if loaded { - return errors.New("pull key already exists") - } - c.InitHlsPlayer() - case movie.Live && movie.Proxy: - if !conf.Conf.Proxy.LiveProxy { - return errors.New("live proxy is not enabled") - } - u, err := url.Parse(movie.Url) - if err != nil { - return err - } - if utils.IsLocalIP(u.Host) { - return errors.New("local ip is not allowed") - } - switch u.Scheme { - case "rtmp": - movie.PullKey = uuid.NewMD5(uuid.NameSpaceURL, []byte(movie.Url)).String() - c, loaded := r.channles.LoadOrStore(movie.PullKey, rtmps.NewChannel()) - if loaded { - return errors.New("pull key already exists") - } - c.InitHlsPlayer() - go func() { - for { - if c.Closed() { - return - } - cli := core.NewConnClient() - if err = cli.Start(movie.Url, av.PLAY); err != nil { - cli.Close() - time.Sleep(time.Second) - continue - } - if err := c.PushStart(rtmpProto.NewReader(cli)); err != nil { - cli.Close() - time.Sleep(time.Second) - } - } - }() - case "http", "https": - if movie.Type != "flv" { - return errors.New("only flv is supported") - } - movie.PullKey = uuid.NewMD5(uuid.NameSpaceURL, []byte(movie.Url)).String() - c, loaded := r.channles.LoadOrStore(movie.PullKey, rtmps.NewChannel()) - if loaded { - return errors.New("pull key already exists") - } - c.InitHlsPlayer() - go func() { - for { - if c.Closed() { - return - } - r := resty.New().R() - for k, v := range movie.Headers { - r.SetHeader(k, v) - } - // r.SetHeader("User-Agent", UserAgent) - resp, err := r.Get(movie.Url) - if err != nil { - time.Sleep(time.Second) - continue - } - if err := c.PushStart(flv.NewReader(resp.RawBody())); err != nil { - time.Sleep(time.Second) - } - resp.RawBody().Close() - } - }() - default: - return errors.New("unsupported scheme") - } - case !movie.Live && movie.RtmpSource: - return errors.New("rtmp source can't be true when movie is not live") - case !movie.Live && movie.Proxy: - if !conf.Conf.Proxy.MovieProxy { - return errors.New("movie proxy is not enabled") - } - u, err := url.Parse(movie.Url) - if err != nil { - return err - } - if utils.IsLocalIP(u.Host) { - return errors.New("local ip is not allowed") - } - if u.Scheme != "http" && u.Scheme != "https" { - return errors.New("unsupported scheme") - } - movie.PullKey = uuid.NewMD5(uuid.NameSpaceURL, []byte(movie.Url)).String() - case !movie.Live && !movie.Proxy, movie.Live && !movie.Proxy && !movie.RtmpSource: - u, err := url.Parse(movie.Url) - if err != nil { - return err - } - if u.Scheme != "http" && u.Scheme != "https" { - return errors.New("unsupported scheme") - } - movie.PullKey = "" - default: - return errors.New("unknown error") - } - return nil + return r.movies.Update(movieId, movie) } func (r *Room) AddMovie(m model.Movie) error { - err := r.LazyInit() - if err != nil { - return err - } - - m.Position = uint(time.Now().UnixMilli()) - m.RoomID = r.ID - - err = r.initMovie(&m) - if err != nil { - return err - } - - err = CreateMovie(&m) - if err != nil { - r.terminateMovie(&m) - } - return err + return r.movies.Add(&m) } func (r *Room) HasPermission(user *model.User, permission model.Permission) bool { @@ -327,49 +120,20 @@ func (r *Room) DeleteUserPermission(userID uint) error { return db.DeleteUserPermission(r.ID, userID) } -func (r *Room) GetMoviesCount() (int, error) { - return GetMoviesCountByRoomID(r.ID) -} - -func (r *Room) GetAllMovies() ([]*model.Movie, error) { - ms, err := GetAllMoviesByRoomID(r.ID) - if err != nil { - return nil, err - } - var m []*model.Movie = make([]*model.Movie, 0, ms.Len()) - for i := ms.Front(); i != nil; i = i.Next() { - m = append(m, i.Value) - } - return m, nil -} - -func (r *Room) GetMoviesByRoomIDWithPage(page, pageSize int) ([]*model.Movie, error) { - return GetMoviesByRoomIDWithPage(r.ID, page, pageSize) -} - -func (r *Room) GetMovieByID(id uint) (*model.Movie, error) { - return GetMovieByID(r.ID, id) +func (r *Room) GetMoviesCount() int { + return r.movies.Len() } func (r *Room) DeleteMovieByID(id uint) error { - r.LazyInit() - m, err := LoadAndDeleteMovieByID(r.ID, id) - if err != nil { - return err - } - return r.terminateMovie(m) + return r.movies.DeleteMovieByID(id) } func (r *Room) ClearMovies() error { - r.LazyInit() - ms, err := db.LoadAndDeleteMoviesByRoomID(r.ID) - if err != nil { - return err - } - for _, m := range ms { - r.terminateMovie(m) - } - return nil + return r.movies.Clear() +} + +func (r *Room) GetMovieByID(id uint) (*movie, error) { + return r.movies.GetMovieByID(id) } func (r *Room) Current() *Current { @@ -378,31 +142,33 @@ func (r *Room) Current() *Current { } func (r *Room) ChangeCurrentMovie(id uint) error { - r.LazyInit() - m, err := GetMovieByID(r.ID, id) + m, err := r.movies.GetMovieByID(id) if err != nil { return err } - r.current.SetMovie(*m) + r.current.SetMovie(*m.Movie) return nil } func (r *Room) SwapMoviePositions(id1, id2 uint) error { - r.LazyInit() - return SwapMoviePositions(r.ID, id1, id2) + return r.movies.SwapMoviePositions(id1, id2) +} + +func (r *Room) GetMovieWithPullKey(pullKey string) (*movie, error) { + return r.movies.GetMovieWithPullKey(pullKey) } -func (r *Room) GetMovieWithPullKey(pullKey string) (*model.Movie, error) { - return GetMovieWithPullKey(r.ID, pullKey) +func (r *Room) GetMoviesWithPage(page, pageSize int) []*movie { + return r.movies.GetMoviesWithPage(page, pageSize) } func (r *Room) RegClient(user *User, conn *websocket.Conn) (*Client, error) { - r.LazyInit() + r.lazyInitHub() return r.hub.RegClient(newClient(user, r, conn)) } func (r *Room) UnregisterClient(user *User) error { - r.LazyInit() + r.lazyInitHub() return r.hub.UnRegClient(user) } diff --git a/internal/op/rooms.go b/internal/op/rooms.go index 533b9fd..047e2df 100644 --- a/internal/op/rooms.go +++ b/internal/op/rooms.go @@ -24,6 +24,9 @@ func InitRoom(room *model.Room) (*Room, error) { Room: *room, version: crc32.ChecksumIEEE(room.HashedPassword), current: newCurrent(), + movies: movies{ + roomID: room.ID, + }, } r, loaded := roomCache.LoadOrStore(room.ID, r) if loaded { @@ -32,14 +35,15 @@ func InitRoom(room *model.Room) (*Room, error) { return r, nil } -func LoadOrInitRoom(room *model.Room) (r *Room, loaded bool) { - r = &Room{ +func LoadOrInitRoom(room *model.Room) (*Room, bool) { + return roomCache.LoadOrStore(room.ID, &Room{ Room: *room, version: crc32.ChecksumIEEE(room.HashedPassword), current: newCurrent(), - } - r, loaded = roomCache.LoadOrStore(room.ID, r) - return + movies: movies{ + roomID: room.ID, + }, + }) } func DeleteRoom(room *Room) error { diff --git a/server/handlers/movie.go b/server/handlers/movie.go index 25e5a98..34805fe 100644 --- a/server/handlers/movie.go +++ b/server/handlers/movie.go @@ -55,11 +55,7 @@ func MovieList(ctx *gin.Context) { return } - m, err := room.GetMoviesByRoomIDWithPage(int(page), int(max)) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) - return - } + m := room.GetMoviesWithPage(page, max) mresp := make([]model.MoviesResp, len(m)) for i, v := range m { @@ -71,15 +67,9 @@ func MovieList(ctx *gin.Context) { } } - i, err := room.GetMoviesCount() - if err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) - return - } - ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ "current": room.Current(), - "total": i, + "total": room.GetMoviesCount(), "movies": mresp, })) } @@ -103,11 +93,7 @@ func Movies(ctx *gin.Context) { return } - m, err := room.GetMoviesByRoomIDWithPage(int(page), int(max)) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) - return - } + m := room.GetMoviesWithPage(int(page), int(max)) mresp := make([]model.MoviesResp, len(m)) for i, v := range m { @@ -119,14 +105,8 @@ func Movies(ctx *gin.Context) { } } - i, err := room.GetMoviesCount() - if err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) - return - } - ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ - "total": i, + "total": room.GetMoviesCount(), "movies": mresp, })) } @@ -173,7 +153,6 @@ func NewPublishKey(ctx *gin.Context) { ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } - movie, err := room.GetMovieByID(req.Id) if err != nil { ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) diff --git a/utils/utils.go b/utils/utils.go index 6b852c0..4a9c30e 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -35,19 +35,23 @@ func RandBytes(n int) []byte { } func GetPageItems[T any](items []T, page, pageSize int) []T { + start, end := GetPageItemsRange(len(items), page, pageSize) + return items[start:end] +} + +func GetPageItemsRange(total, page, pageSize int) (start, end int) { if pageSize <= 0 || page <= 0 { - return nil + return 0, 0 } - start := (page - 1) * pageSize - l := len(items) - if start > l { - start = l + start = (page - 1) * pageSize + if start > total { + start = total } - end := page * pageSize - if end > l { - end = l + end = page * pageSize + if end > total { + end = total } - return items[start:end] + return } func Index[T comparable](items []T, item T) int {