diff --git a/internal/db/movie.go b/internal/db/movie.go index 5a4eab2..070a25c 100644 --- a/internal/db/movie.go +++ b/internal/db/movie.go @@ -11,7 +11,9 @@ func CreateMovie(movie *model.Movie) error { } func CreateMovies(movies []*model.Movie) error { - return db.Create(movies).Error + return db.Transaction(func(tx *gorm.DB) error { + return tx.Create(movies).Error + }) } func GetAllMoviesByRoomID(roomID string) []*model.Movie { @@ -25,6 +27,16 @@ func DeleteMovieByID(roomID, id string) error { return HandleNotFound(err, "room or movie") } +func DeleteMoviesByID(roomID string, ids []string) error { + return db.Transaction(func(tx *gorm.DB) error { + err := tx.Unscoped().Where("room_id = ? AND id IN ?", roomID, ids).Delete(&model.Movie{}).Error + if err != nil { + return HandleNotFound(err, "room or movie") + } + return nil + }) +} + func LoadAndDeleteMovieByID(roomID, id string, columns ...clause.Column) (*model.Movie, error) { movie := &model.Movie{} err := db.Unscoped().Clauses(clause.Returning{Columns: columns}).Where("room_id = ? AND id = ?", roomID, id).Delete(movie).Error @@ -32,14 +44,22 @@ func LoadAndDeleteMovieByID(roomID, id string, columns ...clause.Column) (*model } func DeleteMoviesByRoomID(roomID string) error { - err := db.Unscoped().Where("room_id = ?", roomID).Delete(&model.Movie{}).Error - return HandleNotFound(err, "room") + return db.Transaction(func(tx *gorm.DB) error { + err := tx.Where("room_id = ?", roomID).Delete(&model.Movie{}).Error + if err != nil { + return HandleNotFound(err, "room") + } + return nil + }) } func LoadAndDeleteMoviesByRoomID(roomID string, columns ...clause.Column) ([]*model.Movie, error) { movies := []*model.Movie{} - err := db.Unscoped().Clauses(clause.Returning{Columns: columns}).Where("room_id = ?", roomID).Delete(&movies).Error - return movies, HandleNotFound(err, "room") + err := db.Transaction(func(tx *gorm.DB) error { + err := tx.Unscoped().Clauses(clause.Returning{Columns: columns}).Where("room_id = ?", roomID).Delete(&movies).Error + return HandleNotFound(err, "room") + }) + return movies, err } func UpdateMovie(movie *model.Movie, columns ...clause.Column) error { diff --git a/internal/op/current.go b/internal/op/current.go index fce87e7..46f56d4 100644 --- a/internal/op/current.go +++ b/internal/op/current.go @@ -3,8 +3,6 @@ package op import ( "sync" "time" - - "github.com/synctv-org/synctv/internal/model" ) type current struct { @@ -13,8 +11,9 @@ type current struct { } type Current struct { - Movie model.Movie `json:"movie"` - Status Status `json:"status"` + MovieID string + IsLive bool + Status Status } func newCurrent() *current { @@ -26,10 +25,10 @@ func newCurrent() *current { } type Status struct { - Seek float64 `json:"seek"` - Rate float64 `json:"rate"` - Playing bool `json:"playing"` - lastUpdate time.Time + Seek float64 `json:"seek"` + Rate float64 `json:"rate"` + Playing bool `json:"playing"` + lastUpdate time.Time `json:"-"` } func newStatus() Status { @@ -47,15 +46,12 @@ func (c *current) Current() Current { return c.current } -func (c *current) SetMovie(movie *model.Movie, play bool) { +func (c *current) SetMovie(movieID string, isLive, play bool) { c.lock.Lock() defer c.lock.Unlock() - if movie == nil { - c.current.Movie = model.Movie{} - } else { - c.current.Movie = *movie - } + c.current.MovieID = movieID + c.current.IsLive = isLive c.current.SetSeek(0, 0) c.current.Status.Playing = play } @@ -82,7 +78,7 @@ func (c *current) SetSeekRate(seek, rate, timeDiff float64) Status { } func (c *Current) UpdateStatus() Status { - if c.Movie.Base.Live { + if c.IsLive { c.Status.lastUpdate = time.Now() return c.Status } @@ -102,7 +98,7 @@ func (c *Current) setLiveStatus() Status { } func (c *Current) SetStatus(playing bool, seek, rate, timeDiff float64) Status { - if c.Movie.Base.Live { + if c.IsLive { return c.setLiveStatus() } c.Status.Playing = playing @@ -117,7 +113,7 @@ func (c *Current) SetStatus(playing bool, seek, rate, timeDiff float64) Status { } func (c *Current) SetSeekRate(seek, rate, timeDiff float64) Status { - if c.Movie.Base.Live { + if c.IsLive { return c.setLiveStatus() } if c.Status.Playing { @@ -131,7 +127,7 @@ func (c *Current) SetSeekRate(seek, rate, timeDiff float64) Status { } func (c *Current) SetSeek(seek, timeDiff float64) Status { - if c.Movie.Base.Live { + if c.IsLive { return c.setLiveStatus() } if c.Status.Playing { diff --git a/internal/op/movies.go b/internal/op/movies.go index 9c9845c..bdca43e 100644 --- a/internal/op/movies.go +++ b/internal/op/movies.go @@ -127,7 +127,7 @@ func (m *movies) Clear() error { return err } for e := m.list.Front(); e != nil; e = e.Next() { - e.Value.Terminate() + _ = e.Value.Terminate() } m.list.Clear() return nil @@ -137,7 +137,7 @@ 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() + _ = e.Value.Terminate() } m.list.Clear() return nil @@ -155,13 +155,34 @@ func (m *movies) DeleteMovieByID(id string) error { for e := m.list.Front(); e != nil; e = e.Next() { if e.Value.Movie.ID == id { - m.list.Remove(e).Terminate() + _ = m.list.Remove(e).Terminate() return nil } } return errors.New("movie not found") } +func (m *movies) DeleteMoviesByID(ids []string) error { + m.init() + m.lock.Lock() + defer m.lock.Unlock() + + err := db.DeleteMoviesByID(m.roomID, ids) + if err != nil { + return err + } + + for _, id := range ids { + for e := m.list.Front(); e != nil; e = e.Next() { + if e.Value.Movie.ID == id { + _ = m.list.Remove(e).Terminate() + break + } + } + } + return nil +} + func (m *movies) GetMovieByID(id string) (*Movie, error) { m.lock.RLock() defer m.lock.RUnlock() diff --git a/internal/op/room.go b/internal/op/room.go index da448b4..5cfe573 100644 --- a/internal/op/room.go +++ b/internal/op/room.go @@ -70,6 +70,9 @@ func (r *Room) CheckVersion(version uint32) bool { } func (r *Room) UpdateMovie(movieId string, movie *model.BaseMovie) error { + if r.current.current.MovieID == movieId { + return errors.New("cannot update current movie") + } return r.movies.Update(movieId, movie) } @@ -164,10 +167,27 @@ func (r *Room) GetMoviesCount() int { } func (r *Room) DeleteMovieByID(id string) error { + if r.current.current.MovieID == id { + return errors.New("cannot delete current movie") + } return r.movies.DeleteMovieByID(id) } +func (r *Room) DeleteMoviesByID(ids []string) error { + if r.current.current.MovieID != "" { + for _, id := range ids { + if id == r.current.current.MovieID { + return errors.New("cannot delete current movie") + } + } + } + return r.movies.DeleteMoviesByID(ids) +} + func (r *Room) ClearMovies() error { + if r.current.current.MovieID != "" { + return errors.New("cannot clear movies when current movie is not empty") + } return r.movies.Clear() } @@ -180,11 +200,13 @@ func (r *Room) Current() *Current { return &c } +var ErrNoCurrentMovie = errors.New("no current movie") + func (r *Room) CurrentMovie() (*Movie, error) { - if r.current.current.Movie.ID == "" { - return nil, errors.New("no current movie") + if r.current.current.MovieID == "" { + return nil, ErrNoCurrentMovie } - return r.GetMovieByID(r.current.current.Movie.ID) + return r.GetMovieByID(r.current.current.MovieID) } func (r *Room) CheckCurrentExpired(expireId uint64) (bool, error) { @@ -195,19 +217,19 @@ func (r *Room) CheckCurrentExpired(expireId uint64) (bool, error) { return m.CheckExpired(expireId), nil } -func (r *Room) SetCurrentMovieByID(id string, play bool) error { - m, err := r.movies.GetMovieByID(id) +func (r *Room) SetCurrentMovie(movieID string, play bool) error { + if movieID == "" { + r.current.SetMovie("", false, play) + return nil + } + m, err := r.GetMovieByID(movieID) if err != nil { return err } - r.SetCurrentMovie(&m.Movie, play) + r.current.SetMovie(m.ID, m.Base.Live, play) return nil } -func (r *Room) SetCurrentMovie(movie *model.Movie, play bool) { - r.current.SetMovie(movie, play) -} - func (r *Room) SwapMoviePositions(id1, id2 string) error { return r.movies.SwapMoviePositions(id1, id2) } diff --git a/internal/op/user.go b/internal/op/user.go index cc8eafb..66f03db 100644 --- a/internal/op/user.go +++ b/internal/op/user.go @@ -11,6 +11,7 @@ import ( "github.com/synctv-org/synctv/internal/model" "github.com/synctv-org/synctv/internal/provider" "github.com/synctv-org/synctv/internal/settings" + pb "github.com/synctv-org/synctv/proto/message" "github.com/zijiren233/stream" "golang.org/x/crypto/bcrypt" ) @@ -130,7 +131,17 @@ func (u *User) AddMovieToRoom(room *Room, movie *model.BaseMovie) error { if err != nil { return err } - return room.AddMovie(m) + err = room.AddMovie(m) + if err != nil { + return err + } + return room.Broadcast(&pb.ElementMessage{ + Type: pb.ElementMessageType_MOVIES_CHANGED, + MoviesChanged: &pb.Sender{ + Username: u.Username, + Userid: u.ID, + }, + }) } func (u *User) NewMovies(movies []*model.BaseMovie) ([]*model.Movie, error) { @@ -153,7 +164,17 @@ func (u *User) AddMoviesToRoom(room *Room, movies []*model.BaseMovie) error { if err != nil { return err } - return room.AddMovies(m) + err = room.AddMovies(m) + if err != nil { + return err + } + return room.Broadcast(&pb.ElementMessage{ + Type: pb.ElementMessageType_MOVIES_CHANGED, + MoviesChanged: &pb.Sender{ + Username: u.Username, + Userid: u.ID, + }, + }) } func (u *User) IsRoot() bool { @@ -220,7 +241,17 @@ func (u *User) UpdateMovie(room *Room, movieID string, movie *model.BaseMovie) e if m.Movie.CreatorID != u.ID && !u.HasRoomPermission(room, model.PermissionEditUser) { return model.ErrNoPermission } - return room.UpdateMovie(movieID, movie) + err = room.UpdateMovie(movieID, movie) + if err != nil { + return err + } + return room.Broadcast(&pb.ElementMessage{ + Type: pb.ElementMessageType_MOVIES_CHANGED, + MoviesChanged: &pb.Sender{ + Username: u.Username, + Userid: u.ID, + }, + }) } func (u *User) SetRoomSetting(room *Room, setting model.RoomSettings) error { @@ -251,35 +282,67 @@ func (u *User) DeleteMoviesByID(room *Room, movieIDs []string) error { return model.ErrNoPermission } } - for _, v := range movieIDs { - if err := room.DeleteMovieByID(v); err != nil { - return err - } + if err := room.DeleteMoviesByID(movieIDs); err != nil { + return err } - return nil + return room.Broadcast(&pb.ElementMessage{ + Type: pb.ElementMessageType_MOVIES_CHANGED, + MoviesChanged: &pb.Sender{ + Username: u.Username, + Userid: u.ID, + }, + }) } func (u *User) ClearMovies(room *Room) error { - if !u.HasRoomPermission(room, model.PermissionEditUser) { + if !u.HasRoomPermission(room, model.PermissionEditRoom) { return model.ErrNoPermission } - return room.ClearMovies() + err := room.ClearMovies() + if err != nil { + return err + } + return room.Broadcast(&pb.ElementMessage{ + Type: pb.ElementMessageType_MOVIES_CHANGED, + MoviesChanged: &pb.Sender{ + Username: u.Username, + Userid: u.ID, + }, + }) } -func (u *User) SetCurrentMovie(room *Room, movie *model.Movie, play bool) error { - if !u.HasRoomPermission(room, model.PermissionEditCurrent) { +func (u *User) SwapMoviePositions(room *Room, id1, id2 string) error { + if !u.HasRoomPermission(room, model.PermissionEditRoom) { return model.ErrNoPermission } - room.SetCurrentMovie(movie, play) - return nil + err := room.SwapMoviePositions(id1, id2) + if err != nil { + return err + } + return room.Broadcast(&pb.ElementMessage{ + Type: pb.ElementMessageType_MOVIES_CHANGED, + MoviesChanged: &pb.Sender{ + Username: u.Username, + Userid: u.ID, + }, + }) } -func (u *User) SetCurrentMovieByID(room *Room, movieID string, play bool) error { - m, err := room.GetMovieByID(movieID) +func (u *User) SetCurrentMovie(room *Room, movieID string, play bool) error { + if !u.HasRoomPermission(room, model.PermissionEditCurrent) { + return model.ErrNoPermission + } + err := room.SetCurrentMovie(movieID, play) if err != nil { return err } - return u.SetCurrentMovie(room, &m.Movie, play) + return room.Broadcast(&pb.ElementMessage{ + Type: pb.ElementMessageType_CURRENT_CHANGED, + CurrentChanged: &pb.Sender{ + Username: u.Username, + Userid: u.ID, + }, + }) } func (u *User) BindProvider(p provider.OAuth2Provider, pid string) error { diff --git a/server/handlers/movie.go b/server/handlers/movie.go index 1f72ffa..a2f1314 100644 --- a/server/handlers/movie.go +++ b/server/handlers/movie.go @@ -23,7 +23,6 @@ import ( "github.com/synctv-org/synctv/internal/op" "github.com/synctv-org/synctv/internal/rtmp" "github.com/synctv-org/synctv/internal/settings" - pb "github.com/synctv-org/synctv/proto/message" "github.com/synctv-org/synctv/server/model" "github.com/synctv-org/synctv/utils" "github.com/zijiren233/livelib/protocol/hls" @@ -124,12 +123,12 @@ func genCurrentMovieInfo(ctx context.Context, user *op.User, room *op.Room, opMo } func genCurrentRespWithCurrent(ctx context.Context, user *op.User, room *op.Room, current *op.Current) (*model.CurrentMovieResp, error) { - if current.Movie.ID == "" { + if current.MovieID == "" { return &model.CurrentMovieResp{ Movie: &model.MovieResp{}, }, nil } - opMovie, err := room.GetMovieByID(current.Movie.ID) + opMovie, err := room.GetMovieByID(current.MovieID) if err != nil { return nil, fmt.Errorf("get current movie error: %w", err) } @@ -217,18 +216,6 @@ func PushMovie(ctx *gin.Context) { return } - if err := room.Broadcast(&pb.ElementMessage{ - Type: pb.ElementMessageType_MOVIES_CHANGED, - MoviesChanged: &pb.Sender{ - Username: user.Username, - Userid: user.ID, - }, - }); err != nil { - log.Errorf("push movie error: %v", err) - ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) - return - } - ctx.Status(http.StatusNoContent) } @@ -261,18 +248,6 @@ func PushMovies(ctx *gin.Context) { return } - if err := room.Broadcast(&pb.ElementMessage{ - Type: pb.ElementMessageType_MOVIES_CHANGED, - MoviesChanged: &pb.Sender{ - Username: user.Username, - Userid: user.ID, - }, - }); err != nil { - log.Errorf("push movies error: %v", err) - ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) - return - } - ctx.Status(http.StatusNoContent) } @@ -357,18 +332,6 @@ func EditMovie(ctx *gin.Context) { return } - if err := room.Broadcast(&pb.ElementMessage{ - Type: pb.ElementMessageType_MOVIES_CHANGED, - MoviesChanged: &pb.Sender{ - Username: user.Username, - Userid: user.ID, - }, - }); err != nil { - log.Errorf("edit movie error: %v", err) - ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) - return - } - ctx.Status(http.StatusNoContent) } @@ -395,17 +358,6 @@ func DelMovie(ctx *gin.Context) { return } - if err := room.Broadcast(&pb.ElementMessage{ - Type: pb.ElementMessageType_MOVIES_CHANGED, - MoviesChanged: &pb.Sender{ - Username: user.Username, - Userid: user.ID, - }, - }); err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) - return - } - ctx.Status(http.StatusNoContent) } @@ -422,17 +374,6 @@ func ClearMovies(ctx *gin.Context) { return } - if err := room.Broadcast(&pb.ElementMessage{ - Type: pb.ElementMessageType_MOVIES_CHANGED, - MoviesChanged: &pb.Sender{ - Username: user.Username, - Userid: user.ID, - }, - }); err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) - return - } - ctx.Status(http.StatusNoContent) } @@ -446,22 +387,11 @@ func SwapMovie(ctx *gin.Context) { return } - if err := room.SwapMoviePositions(req.Id1, req.Id2); err != nil { + if err := user.SwapMoviePositions(room, req.Id1, req.Id2); err != nil { ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } - if err := room.Broadcast(&pb.ElementMessage{ - Type: pb.ElementMessageType_MOVIES_CHANGED, - MoviesChanged: &pb.Sender{ - Username: user.Username, - Userid: user.ID, - }, - }); err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) - return - } - ctx.Status(http.StatusNoContent) } @@ -477,24 +407,7 @@ func ChangeCurrentMovie(ctx *gin.Context) { return } - if req.Id == "" { - err = user.SetCurrentMovie(room, nil, false) - } else { - var movie *op.Movie - movie, err = room.GetMovieByID(req.Id) - if err != nil { - log.Errorf("change current movie error: %v", err) - ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) - return - } - _, err = genCurrentMovieInfo(ctx, user, room, movie) - if err != nil { - log.Errorf("change current movie error: %v", err) - ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) - return - } - err = user.SetCurrentMovie(room, &movie.Movie, true) - } + err = user.SetCurrentMovie(room, req.Id, false) if err != nil { log.Errorf("change current movie error: %v", err) if errors.Is(err, dbModel.ErrNoPermission) { @@ -505,18 +418,6 @@ func ChangeCurrentMovie(ctx *gin.Context) { return } - if err := room.Broadcast(&pb.ElementMessage{ - Type: pb.ElementMessageType_CURRENT_CHANGED, - CurrentChanged: &pb.Sender{ - Username: user.Username, - Userid: user.ID, - }, - }); err != nil { - log.Errorf("change current movie error: %v", err) - ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) - return - } - ctx.Status(http.StatusNoContent) } diff --git a/server/handlers/websocket.go b/server/handlers/websocket.go index b89a3a4..92a769f 100644 --- a/server/handlers/websocket.go +++ b/server/handlers/websocket.go @@ -225,8 +225,8 @@ func handleElementMsg(cli *op.Client, msg *pb.ElementMessage, l *logrus.Entry) e }) case pb.ElementMessageType_CHECK: current := cli.Room().Current() - if msg.CheckReq.ExpireId != 0 { - currentMovie, err := cli.Room().GetMovieByID(current.Movie.ID) + if msg.CheckReq.ExpireId != 0 && current.MovieID != "" { + currentMovie, err := cli.Room().GetMovieByID(current.MovieID) if err != nil { _ = cli.Send(&pb.ElementMessage{ Type: pb.ElementMessageType_ERROR,