WIP: Unify function to move rows within/between playlists

This commit is contained in:
Keith Edmunds 2025-03-28 09:57:36 +00:00
parent 3c7fc20e5a
commit 2bf1bc64e7
2 changed files with 63 additions and 107 deletions

View File

@ -283,20 +283,19 @@ def _check_playlist_integrity(
if fix:
log.debug(msg)
plr.row_number = idx
session.commit()
else:
raise ApplicationError(msg)
def _move_rows(
session: Session, playlist_id: int, starting_row: int, move_by: int
def _shift_rows(
session: Session, playlist_id: int, starting_row: int, shift_by: int
) -> None:
"""
Move rows from starting_row by move_by. If move_by is +ve, move rows
down; if -ve, move them up.
Shift rows from starting_row by shift_by. If shift_by is +ve, shift rows
down; if -ve, shift them up.
"""
log.debug(f"(_move_rows_down({playlist_id=}, {starting_row=}, {move_by=}")
log.debug(f"(_shift_rows_down({playlist_id=}, {starting_row=}, {shift_by=}")
session.execute(
update(PlaylistRows)
@ -304,32 +303,49 @@ def _move_rows(
(PlaylistRows.playlist_id == playlist_id),
(PlaylistRows.row_number >= starting_row),
)
.values(row_number=PlaylistRows.row_number + move_by)
.values(row_number=PlaylistRows.row_number + shift_by)
)
session.commit()
def move_rows_to_playlist(
from_rows: list[int], from_playlist_id: int, to_row: int, to_playlist_id: int
def move_rows(
from_rows: list[int], from_playlist_id: int, to_row: int, to_playlist_id: int | None = None
) -> None:
"""
Move rows between playlists.
Move rows with or between playlists.
Algorithm:
- Sanity check row numbers
- Check there are no playlist rows with playlist_id == PENDING_MOVE
- Put rows to be moved into PENDING_MOVE playlist
- Resequence remaining row numbers
- Make space for moved rows
- Move the PENDING_MOVE rows back and fixup row numbers
- Sanity check row numbers
"""
log.debug(
f"move_rows_to_playlist({from_rows=}, {from_playlist_id=}, {to_row=}, {to_playlist_id=})"
)
# If to_playlist_id isn't specified, we're moving within the one
# playlist.
if to_playlist_id is None:
to_playlist_id = from_playlist_id
with db.Session() as session:
# Sanity check row numbers
_check_playlist_integrity(session, from_playlist_id, fix=False)
_check_playlist_integrity(session, to_playlist_id, fix=False)
if from_playlist_id != to_playlist_id:
_check_playlist_integrity(session, to_playlist_id, fix=False)
# Check there are no playlist rows with playlist_id == PENDING_MOVE
pending_move_rows = get_playlist_rows(Config.PLAYLIST_PENDING_MOVE)
if pending_move_rows:
raise ApplicationError(f"move_rows_to_playlist: {pending_move_rows=}")
# We need playlist length if we're moving within a playlist. Get
# that now before we remove rows.
from_playlist_length = len(get_playlist_rows(from_playlist_id))
# Put rows to be moved into PENDING_MOVE playlist
session.execute(
update(PlaylistRows)
@ -339,17 +355,30 @@ def move_rows_to_playlist(
)
.values(playlist_id=Config.PLAYLIST_PENDING_MOVE)
)
session.commit()
# Resequence remaining row numbers
_check_playlist_integrity(session, from_playlist_id, fix=True)
session.commit()
# Make space for moved rows.
_move_rows(session, to_playlist_id, to_row, len(from_rows))
# Make space for moved rows. If moving within one playlist,
# determning where to make the space is non-trivial. For example,
# if the playlist has ten entries and we're moving four of them
# to row 8, after we've moved the rows to the
# PLAYLIST_PENDING_MOVE there will only be six entries left.
# Clearly we can't make space at row 8...
space_row = to_row
if to_playlist_id == from_playlist_id:
overflow = max(to_row + len(from_rows) - from_playlist_length, 0)
if overflow != 0:
space_row = (
to_row - overflow - len([a for a in from_rows if a > to_row])
)
_shift_rows(session, to_playlist_id, space_row, len(from_rows))
# Move the PENDING_MOVE rows back and fixup row numbers
update_list: list[dict[str, int]] = []
next_row = to_row
next_row = space_row
# PLAYLIST_PENDING_MOVE may have gaps so don't check it
for row_to_move in get_playlist_rows(
Config.PLAYLIST_PENDING_MOVE, check_playlist_itegrity=False
@ -365,92 +394,16 @@ def move_rows_to_playlist(
session.commit()
# Sanity check row numbers
_check_playlist_integrity(session, from_playlist_id, fix=False)
_check_playlist_integrity(session, to_playlist_id, fix=False)
def move_rows_within_playlist(
playlist_id: int, from_rows: list[int], to_row: int
) -> None:
"""
Move rows within a playlist.
Algorithm:
- Sanity check row numbers
- Check there are no playlist rows with playlist_id == PENDING_MOVE
- Put rows to be moved into PENDING_MOVE playlist
- Resequence remaining row numbers
- Make space for moved rows
- Move the PENDING_MOVE rows back and fixup row numbers
- Sanity check row numbers
"""
log.debug(f"move_rows_within_playlist({playlist_id=}, {from_rows=}, {to_row=})")
with db.Session() as session:
# Sanity check row numbers
_check_playlist_integrity(session, playlist_id, fix=False)
# Check there are no playlist rows with playlist_id == PENDING_MOVE
pending_move_rows = get_playlist_rows(Config.PLAYLIST_PENDING_MOVE)
if pending_move_rows:
raise ApplicationError(f"move_rows_within_playlist: {pending_move_rows=}")
# Get length of playlist
playlist_length = len(get_playlist_rows(playlist_id))
# Put rows to be moved into PENDING_MOVE playlist
session.execute(
update(PlaylistRows)
.where(
PlaylistRows.playlist_id == playlist_id,
PlaylistRows.row_number.in_(from_rows),
)
.values(playlist_id=Config.PLAYLIST_PENDING_MOVE)
)
session.commit()
# Resequence remaining row numbers
_check_playlist_integrity(session, playlist_id, fix=True)
# Make space for moved rows. Determning where to make the space
# is non-trivial. For example, if the playlist has ten entries
# and we're moving four of them to row 8, after we've moved the
# rows to the PLAYLIST_PENDING_MOVE there will only be six
# entries left. Clearly we can't make space at row 8...
overflow = max(to_row + len(from_rows) - playlist_length, 0)
if overflow == 0:
space_row = to_row
else:
space_row = to_row - overflow - len([a for a in from_rows if a > to_row])
_move_rows(session, playlist_id, space_row, len(from_rows))
# Move the PENDING_MOVE rows back and fixup row numbers
update_list: list[dict[str, int]] = []
next_row = space_row
# PLAYLIST_PENDING_MOVE may have gaps so don't check it
for row_to_move in get_playlist_rows(
Config.PLAYLIST_PENDING_MOVE, check_playlist_itegrity=False
):
update_list.append(
{"id": row_to_move.playlistrow_id, "row_number": next_row}
)
update_list.append(
{"id": row_to_move.playlistrow_id, "playlist_id": playlist_id}
)
next_row += 1
session.execute(update(PlaylistRows), update_list)
session.commit()
# Sanity check row numbers
_check_playlist_integrity(session, playlist_id, fix=False)
_check_playlist_integrity(session, from_playlist_id, fix=False)
if from_playlist_id != to_playlist_id:
_check_playlist_integrity(session, to_playlist_id, fix=False)
def update_row_numbers(
playlist_id: int, id_to_row_number: list[dict[int, int]]
) -> None:
"""
Update playlistrows rownumbers for pass playlistrow_ids
Update playlistrows rownumbers for passed playlistrow_ids
playlist_id is only needed for sanity checking
"""
@ -683,8 +636,11 @@ def insert_row(
_check_playlist_integrity(session, playlist_id, fix=False)
# Make space for new row
_move_rows(
session=session, playlist_id=playlist_id, starting_row=row_number, move_by=1
_shift_rows(
session=session,
playlist_id=playlist_id,
starting_row=row_number,
shift_by=1,
)
playlist_row = PlaylistRows.insert_row(

View File

@ -135,7 +135,7 @@ class MyTestCase(unittest.TestCase):
number_of_rows = 10
(playlist, model) = self.create_rows("test_move_rows_test1", number_of_rows)
repository.move_rows_within_playlist(playlist.playlist_id, [3], 5)
repository.move_rows([3], playlist.playlist_id, 5)
# Check we have all rows and plr_rownums are correct
new_order = []
@ -149,7 +149,7 @@ class MyTestCase(unittest.TestCase):
number_of_rows = 10
(playlist, model) = self.create_rows("test_move_rows_test2", number_of_rows)
repository.move_rows_within_playlist(playlist.playlist_id, [4], 3)
repository.move_rows([4], playlist.playlist_id, 3)
# Check we have all rows and plr_rownums are correct
new_order = []
@ -163,7 +163,7 @@ class MyTestCase(unittest.TestCase):
number_of_rows = 10
(playlist, model) = self.create_rows("test_move_rows_test3", number_of_rows)
repository.move_rows_within_playlist(playlist.playlist_id, [4], 2)
repository.move_rows([4], playlist.playlist_id, 2)
# Check we have all rows and plr_rownums are correct
new_order = []
@ -177,7 +177,7 @@ class MyTestCase(unittest.TestCase):
number_of_rows = 11
(playlist, model) = self.create_rows("test_move_rows_test4", number_of_rows)
repository.move_rows_within_playlist(playlist.playlist_id, [1, 4, 5, 10], 8)
repository.move_rows([1, 4, 5, 10], playlist.playlist_id, 8)
# Check we have all rows and plr_rownums are correct
new_order = []
@ -191,7 +191,7 @@ class MyTestCase(unittest.TestCase):
number_of_rows = 11
(playlist, model) = self.create_rows("test_move_rows_test5", number_of_rows)
repository.move_rows_within_playlist(playlist.playlist_id, [3, 6], 5)
repository.move_rows([3, 6], playlist.playlist_id, 5)
# Check we have all rows and plr_rownums are correct
new_order = []
@ -205,7 +205,7 @@ class MyTestCase(unittest.TestCase):
number_of_rows = 11
(playlist, model) = self.create_rows("test_move_rows_test6", number_of_rows)
repository.move_rows_within_playlist(playlist.playlist_id, [3, 5, 6], 8)
repository.move_rows([3, 5, 6], playlist.playlist_id, 8)
# Check we have all rows and plr_rownums are correct
new_order = []
@ -219,7 +219,7 @@ class MyTestCase(unittest.TestCase):
number_of_rows = 11
(playlist, model) = self.create_rows("test_move_rows_test7", number_of_rows)
repository.move_rows_within_playlist(playlist.playlist_id, [7, 8, 10], 5)
repository.move_rows([7, 8, 10], playlist.playlist_id, 5)
# Check we have all rows and plr_rownums are correct
new_order = []
@ -234,7 +234,7 @@ class MyTestCase(unittest.TestCase):
number_of_rows = 11
(playlist, model) = self.create_rows("test_move_rows_test8", number_of_rows)
repository.move_rows_within_playlist(playlist.playlist_id, [0, 1, 2, 3], 0)
repository.move_rows([0, 1, 2, 3], playlist.playlist_id, 0)
# Check we have all rows and plr_rownums are correct
new_order = []
@ -250,7 +250,7 @@ class MyTestCase(unittest.TestCase):
(playlist_src, model_src) = self.create_rows("src playlist", number_of_rows)
(playlist_dst, model_dst) = self.create_rows("dst playlist", number_of_rows)
repository.move_rows_to_playlist(
repository.move_rows(
rows_to_move, playlist_src.playlist_id, to_row, playlist_dst.playlist_id
)