fix(playlists): overlapping writes, and what the second review found
A read from before a write no longer brings a row back: a write calls off the read on its way, what lands while one is out stays off the screen, and the last write out does the refresh. A failure puts back its own rows, an accepted reorder brings back none, and an Undo reads past the cache. A list the cache has dropped is read before a removal, so its Undo has places. undo-move refuses the same list twice, positions cannot repeat, and undo-move and the Plex key bodies refuse a body without their videos. Focus goes to the header's box when the last shown row or Save this order goes.
This commit is contained in:
@@ -420,11 +420,14 @@ class VideoIdsIn(BaseModel):
|
||||
|
||||
|
||||
def _paired(video_count: int, positions: list[int]) -> None:
|
||||
"""One place per video, none before the start: both undo bodies come back from the client."""
|
||||
"""One place per video, none before the start and none twice: both undo bodies come back from
|
||||
the client, and the insert's arithmetic counts on places that only rise."""
|
||||
if len(positions) != video_count:
|
||||
raise ValueError("positions must pair one-to-one with the videos")
|
||||
if any(at < 0 for at in positions):
|
||||
raise ValueError("positions cannot be negative")
|
||||
if len(set(positions)) != len(positions):
|
||||
raise ValueError("positions cannot repeat")
|
||||
|
||||
|
||||
class RestoreItemsIn(VideoIdsIn):
|
||||
@@ -443,10 +446,11 @@ class MoveItemsIn(VideoIdsIn):
|
||||
|
||||
class UndoMoveIn(BaseModel):
|
||||
from_playlist_id: int
|
||||
moved: list[str] = []
|
||||
# Required, as `video_ids` is. An empty one is still a real body: the target held them all.
|
||||
moved: list[str]
|
||||
already_there: list[str] = []
|
||||
# One per video of `moved` + `already_there`, in that order.
|
||||
positions: list[int] = []
|
||||
positions: list[int]
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _one_position_per_video(self):
|
||||
@@ -459,6 +463,10 @@ def _list_gone(e: service.ListGone) -> HTTPException:
|
||||
return not_found("playlist") if e.source else not_found("move_target")
|
||||
|
||||
|
||||
def _move_refused(e: service.MoveRefused) -> HTTPException:
|
||||
return HTTPException(status_code=400, detail={"code": "playlist_move", "reason": e.reason})
|
||||
|
||||
|
||||
def _other_list(db: Session, user: User, playlist_id: int) -> "Playlist":
|
||||
"""The other list of a move. Its own reason: "this playlist doesn't exist any more" about a list
|
||||
that is gone was read as the one on screen, which is still there."""
|
||||
@@ -515,9 +523,7 @@ def move_items(
|
||||
try:
|
||||
moved, already_there = service.move_items(db, src, dst, body.video_ids)
|
||||
except service.MoveRefused as e:
|
||||
raise HTTPException(
|
||||
status_code=400, detail={"code": "playlist_move", "reason": e.reason}
|
||||
) from None
|
||||
raise _move_refused(e) from None
|
||||
except service.ListGone as e:
|
||||
raise _list_gone(e) from None
|
||||
db.commit()
|
||||
@@ -542,6 +548,8 @@ def undo_move(
|
||||
dst = _other_list(db, user, body.from_playlist_id)
|
||||
try:
|
||||
added = service.undo_move(db, src, dst, body.moved, body.already_there, body.positions)
|
||||
except service.MoveRefused as e:
|
||||
raise _move_refused(e) from None
|
||||
except service.ListGone as e:
|
||||
raise _list_gone(e) from None
|
||||
db.commit()
|
||||
|
||||
Reference in New Issue
Block a user