Compare commits
9 Commits
8fa98a2207
...
c6be215bd4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6be215bd4 | ||
|
|
22e16144e1 | ||
|
|
1932719fea | ||
|
|
e9f4ecf5ef | ||
|
|
ce224a41d1 | ||
|
|
bf89172f8a | ||
|
|
9f234bb007 | ||
|
|
6fb6339843 | ||
|
|
bf5563f2f1 |
@ -1,4 +1,6 @@
|
|||||||
# Standard library imports
|
# Standard library imports
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
# PyQt imports
|
# PyQt imports
|
||||||
|
|
||||||
@ -6,6 +8,7 @@
|
|||||||
from alchemical import Alchemical # type:ignore
|
from alchemical import Alchemical # type:ignore
|
||||||
|
|
||||||
# App imports
|
# App imports
|
||||||
|
from config import Config
|
||||||
|
|
||||||
|
|
||||||
class DatabaseManager:
|
class DatabaseManager:
|
||||||
@ -29,3 +32,12 @@ class DatabaseManager:
|
|||||||
if DatabaseManager.__instance is None:
|
if DatabaseManager.__instance is None:
|
||||||
DatabaseManager(database_url, **kwargs)
|
DatabaseManager(database_url, **kwargs)
|
||||||
return DatabaseManager.__instance
|
return DatabaseManager.__instance
|
||||||
|
|
||||||
|
|
||||||
|
# Establish database connection
|
||||||
|
DATABASE_URL = os.environ.get("DATABASE_URL")
|
||||||
|
if DATABASE_URL is None:
|
||||||
|
raise ValueError("DATABASE_URL is undefined")
|
||||||
|
if "unittest" in sys.modules and "sqlite" not in DATABASE_URL:
|
||||||
|
raise ValueError("Unit tests running on non-Sqlite database")
|
||||||
|
db = DatabaseManager.get_instance(DATABASE_URL, engine_options=Config.ENGINE_OPTIONS).db
|
||||||
|
|||||||
249
app/ds.py
249
app/ds.py
@ -29,9 +29,9 @@ from classes import (
|
|||||||
TrackDTO,
|
TrackDTO,
|
||||||
)
|
)
|
||||||
from config import Config
|
from config import Config
|
||||||
|
from dbmanager import db
|
||||||
from log import log, log_call
|
from log import log, log_call
|
||||||
from models import (
|
from models import (
|
||||||
db,
|
|
||||||
NoteColours,
|
NoteColours,
|
||||||
Playdates,
|
Playdates,
|
||||||
PlaylistRows,
|
PlaylistRows,
|
||||||
@ -44,8 +44,8 @@ from models import (
|
|||||||
|
|
||||||
# Configure the dogpile cache region
|
# Configure the dogpile cache region
|
||||||
cache_region = make_region().configure(
|
cache_region = make_region().configure(
|
||||||
'dogpile.cache.memory', # Use in-memory caching for now (switch to Redis if needed)
|
"dogpile.cache.memory", # Use in-memory caching for now (switch to Redis if needed)
|
||||||
expiration_time=600 # Cache expires after 10 minutes
|
expiration_time=600, # Cache expires after 10 minutes
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -179,7 +179,9 @@ def notecolours_remove_colour_substring(text: str) -> str:
|
|||||||
|
|
||||||
# Track functions
|
# Track functions
|
||||||
# @log_call
|
# @log_call
|
||||||
def _tracks_where(query: BinaryExpression | ColumnElement[bool],) -> list[TrackDTO]:
|
def _tracks_where(
|
||||||
|
query: BinaryExpression | ColumnElement[bool],
|
||||||
|
) -> list[TrackDTO]:
|
||||||
"""
|
"""
|
||||||
filter_by_last_played: bool = False,
|
filter_by_last_played: bool = False,
|
||||||
last_played_before: dt.datetime | None = None,
|
last_played_before: dt.datetime | None = None,
|
||||||
@ -435,9 +437,7 @@ def tracks_filtered(filter: Filter) -> list[TrackDTO]:
|
|||||||
|
|
||||||
|
|
||||||
# @log_call
|
# @log_call
|
||||||
def track_update(
|
def track_update(track_id: int, metadata: dict[str, str | int | float]) -> TrackDTO:
|
||||||
track_id: int, metadata: dict[str, str | int | float]
|
|
||||||
) -> TrackDTO:
|
|
||||||
"""
|
"""
|
||||||
Update an existing track db entry return the DTO
|
Update an existing track db entry return the DTO
|
||||||
"""
|
"""
|
||||||
@ -472,6 +472,8 @@ def _playlist_check_playlist(
|
|||||||
else raise ApplicationError.
|
else raise ApplicationError.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
fixed = False
|
||||||
|
|
||||||
playlist_rows = (
|
playlist_rows = (
|
||||||
session.execute(
|
session.execute(
|
||||||
select(PlaylistRows)
|
select(PlaylistRows)
|
||||||
@ -492,9 +494,13 @@ def _playlist_check_playlist(
|
|||||||
if fix:
|
if fix:
|
||||||
log.debug(msg)
|
log.debug(msg)
|
||||||
plr.row_number = idx
|
plr.row_number = idx
|
||||||
|
fixed = True
|
||||||
else:
|
else:
|
||||||
raise ApplicationError(msg)
|
raise ApplicationError(msg)
|
||||||
|
|
||||||
|
if fixed:
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
# @log_call
|
# @log_call
|
||||||
def _playlist_shift_rows(
|
def _playlist_shift_rows(
|
||||||
@ -523,13 +529,17 @@ def _playlists_where(
|
|||||||
Return playlists selected by query
|
Return playlists selected by query
|
||||||
"""
|
"""
|
||||||
|
|
||||||
stmt = select(
|
stmt = (
|
||||||
Playlists.favourite,
|
select(
|
||||||
Playlists.is_template,
|
Playlists.favourite,
|
||||||
Playlists.id.label("playlist_id"),
|
Playlists.is_template,
|
||||||
Playlists.name,
|
Playlists.id.label("playlist_id"),
|
||||||
Playlists.open,
|
Playlists.name,
|
||||||
).where(query).order_by(Playlists.tab)
|
Playlists.open,
|
||||||
|
)
|
||||||
|
.where(query)
|
||||||
|
.order_by(Playlists.tab)
|
||||||
|
)
|
||||||
|
|
||||||
results: list[PlaylistDTO] = []
|
results: list[PlaylistDTO] = []
|
||||||
|
|
||||||
@ -597,7 +607,9 @@ def playlists_closed() -> list[PlaylistDTO]:
|
|||||||
|
|
||||||
|
|
||||||
# @log_call
|
# @log_call
|
||||||
def playlist_create(name: str, template_id: int, as_template: bool = False) -> PlaylistDTO:
|
def playlist_create(
|
||||||
|
name: str, template_id: int, as_template: bool = False
|
||||||
|
) -> PlaylistDTO:
|
||||||
"""
|
"""
|
||||||
Create playlist and return DTO.
|
Create playlist and return DTO.
|
||||||
"""
|
"""
|
||||||
@ -676,9 +688,7 @@ def playlist_mark_status(playlist_id: int, open: bool) -> None:
|
|||||||
|
|
||||||
with db.Session() as session:
|
with db.Session() as session:
|
||||||
session.execute(
|
session.execute(
|
||||||
update(Playlists)
|
update(Playlists).where(Playlists.id == playlist_id).values(open=open)
|
||||||
.where(Playlists.id == playlist_id)
|
|
||||||
.values(open=open)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
@ -692,88 +702,145 @@ def playlist_move_rows(
|
|||||||
to_playlist_id: int | None = None,
|
to_playlist_id: int | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Move rows with or between playlists.
|
Call helper function depending upon whether we are moving rows within
|
||||||
|
a playlist or between playlists.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# If to_playlist_id isn't specified, we're moving within the one
|
||||||
|
# playlist.
|
||||||
|
if to_playlist_id is None or to_playlist_id == from_playlist_id:
|
||||||
|
_playlist_move_rows_within_playlist(from_rows, from_playlist_id, to_row)
|
||||||
|
else:
|
||||||
|
_playlist_move_rows_between_playlists(
|
||||||
|
from_rows, from_playlist_id, to_row, to_playlist_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _playlist_move_rows_between_playlists(
|
||||||
|
from_rows: list[int],
|
||||||
|
from_playlist_id: int,
|
||||||
|
to_row: int,
|
||||||
|
to_playlist_id: int,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Move rows between playlists.
|
||||||
|
|
||||||
Algorithm:
|
Algorithm:
|
||||||
- Sanity check row numbers
|
- 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
|
- Resequence remaining row numbers
|
||||||
- Make space for moved rows
|
- Make space for moved rows
|
||||||
- Move the PENDING_MOVE rows back and fixup row numbers
|
- Move the PENDING_MOVE rows back and fixup row numbers
|
||||||
- Sanity check row numbers
|
- Sanity check row numbers
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# If to_playlist_id isn't specified, we're moving within the one
|
# Sanity check destination not being moved
|
||||||
# playlist.
|
if to_row in from_rows:
|
||||||
if to_playlist_id is None:
|
log.error(
|
||||||
to_playlist_id = from_playlist_id
|
f"ds._playlist_move_rows_within_playlist: {to_row=} in {from_rows=}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
with db.Session() as session:
|
with db.Session() as session:
|
||||||
# Sanity check row numbers
|
# Sanity check row numbers
|
||||||
_playlist_check_playlist(session, from_playlist_id, fix=False)
|
_playlist_check_playlist(session, from_playlist_id, fix=False)
|
||||||
if from_playlist_id != to_playlist_id:
|
_playlist_check_playlist(session, to_playlist_id, fix=False)
|
||||||
_playlist_check_playlist(session, to_playlist_id, fix=False)
|
|
||||||
|
|
||||||
# Check there are no playlist rows with playlist_id == PENDING_MOVE
|
# Make space in destination playlist
|
||||||
pending_move_rows = playlistrows_by_playlist(Config.PLAYLIST_PENDING_MOVE)
|
_playlist_shift_rows(session, to_playlist_id, to_row, len(from_rows))
|
||||||
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
|
# Update database
|
||||||
# that now before we remove rows.
|
# Build a dictionary of changes to make
|
||||||
from_playlist_length = len(playlistrows_by_playlist(from_playlist_id))
|
|
||||||
# Put rows to be moved into PENDING_MOVE playlist
|
|
||||||
session.execute(
|
|
||||||
update(PlaylistRows)
|
|
||||||
.where(
|
|
||||||
PlaylistRows.playlist_id == from_playlist_id,
|
|
||||||
PlaylistRows.row_number.in_(from_rows),
|
|
||||||
)
|
|
||||||
.values(playlist_id=Config.PLAYLIST_PENDING_MOVE)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Resequence remaining row numbers
|
|
||||||
_playlist_check_playlist(session, from_playlist_id, fix=True)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
# 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])
|
|
||||||
)
|
|
||||||
|
|
||||||
_playlist_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]] = []
|
update_list: list[dict[str, int]] = []
|
||||||
next_row = space_row
|
old_row_to_id = _playlist_rows_to_id(from_playlist_id)
|
||||||
# PLAYLIST_PENDING_MOVE may have gaps so don't check it
|
next_row = to_row
|
||||||
for row_to_move in playlistrows_by_playlist(
|
|
||||||
Config.PLAYLIST_PENDING_MOVE, check_playlist_itegrity=False
|
for from_row in from_rows:
|
||||||
):
|
plrid = old_row_to_id[from_row]
|
||||||
update_list.append(
|
update_list.append(
|
||||||
{"id": row_to_move.playlistrow_id, "row_number": next_row}
|
{"id": plrid, "row_number": next_row}
|
||||||
)
|
)
|
||||||
update_list.append(
|
update_list.append(
|
||||||
{"id": row_to_move.playlistrow_id, "playlist_id": to_playlist_id}
|
{"id": plrid, "playlist_id": to_playlist_id}
|
||||||
)
|
)
|
||||||
next_row += 1
|
next_row += 1
|
||||||
|
|
||||||
|
session.execute(update(PlaylistRows), update_list)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
# Resequence row numbers in source
|
||||||
|
_playlist_check_playlist(session, from_playlist_id, fix=True)
|
||||||
|
# Sanity check destionation
|
||||||
|
_playlist_check_playlist(session, from_playlist_id, fix=False)
|
||||||
|
|
||||||
|
|
||||||
|
def _playlist_rows_to_id(playlist_id: int) -> dict[int, int]:
|
||||||
|
"""
|
||||||
|
Return a dict of {row_number: playlistrow_id} for passed playlist
|
||||||
|
"""
|
||||||
|
|
||||||
|
row_to_id = {
|
||||||
|
p.row_number: p.playlistrow_id
|
||||||
|
for p in playlistrows_by_playlist(playlist_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return row_to_id
|
||||||
|
|
||||||
|
|
||||||
|
# @log_call
|
||||||
|
def _playlist_move_rows_within_playlist(
|
||||||
|
from_rows: list[int],
|
||||||
|
from_playlist_id: int,
|
||||||
|
to_row: int,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Move rows within playlists.
|
||||||
|
|
||||||
|
Algorithm:
|
||||||
|
- Sanity checks
|
||||||
|
- Create a list of row numbers in the new order
|
||||||
|
- Update the database with the new order
|
||||||
|
- Sanity check row numbers
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Sanity check destination not being moved
|
||||||
|
if to_row in from_rows:
|
||||||
|
log.error(
|
||||||
|
f"ds._playlist_move_rows_within_playlist: {to_row=} in {from_rows=}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
with db.Session() as session:
|
||||||
|
# Sanity check row numbers
|
||||||
|
_playlist_check_playlist(session, from_playlist_id, fix=False)
|
||||||
|
|
||||||
|
# Create a list showing the new order of rows in playlist
|
||||||
|
# Start with a list of rows excluding those to be moved
|
||||||
|
from_playlist_length = len(playlistrows_by_playlist(from_playlist_id))
|
||||||
|
new_row_order = [a for a in range(from_playlist_length) if a not in from_rows]
|
||||||
|
# Insert the moved row numbers
|
||||||
|
try:
|
||||||
|
idx = new_row_order.index(to_row)
|
||||||
|
except ValueError:
|
||||||
|
raise ApplicationError(f"Can't find {to_row=} in {new_row_order=}")
|
||||||
|
new_row_order[idx:idx] = from_rows
|
||||||
|
|
||||||
|
# Update database
|
||||||
|
# Build a dictionary of {old_row_number: new_row_number} where
|
||||||
|
# they differ
|
||||||
|
row_changes = {old: new for new, old in enumerate(new_row_order) if old != new}
|
||||||
|
# Build a dictionary of changes to make
|
||||||
|
update_list: list[dict[str, int]] = []
|
||||||
|
old_row_to_id = _playlist_rows_to_id(from_playlist_id)
|
||||||
|
for old_row, new_row in row_changes.items():
|
||||||
|
plrid = old_row_to_id[old_row]
|
||||||
|
update_list.append({"id": plrid, "row_number": new_row})
|
||||||
|
|
||||||
|
# Updte database
|
||||||
session.execute(update(PlaylistRows), update_list)
|
session.execute(update(PlaylistRows), update_list)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
# Sanity check row numbers
|
# Sanity check row numbers
|
||||||
_playlist_check_playlist(session, from_playlist_id, fix=False)
|
_playlist_check_playlist(session, from_playlist_id, fix=False)
|
||||||
if from_playlist_id != to_playlist_id:
|
|
||||||
_playlist_check_playlist(session, to_playlist_id, fix=False)
|
|
||||||
|
|
||||||
|
|
||||||
def playlists_open() -> list[PlaylistDTO]:
|
def playlists_open() -> list[PlaylistDTO]:
|
||||||
@ -791,9 +858,7 @@ def playlist_rename(playlist_id: int, new_name: str) -> None:
|
|||||||
|
|
||||||
with db.Session() as session:
|
with db.Session() as session:
|
||||||
session.execute(
|
session.execute(
|
||||||
update(Playlists)
|
update(Playlists).where(Playlists.id == playlist_id).values(name=new_name)
|
||||||
.where(Playlists.id == playlist_id)
|
|
||||||
.values(name=new_name)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
@ -903,7 +968,6 @@ def playlist_remove_rows(playlist_id: int, row_numbers: list[int]) -> None:
|
|||||||
)
|
)
|
||||||
# Fixup row number to remove gaps
|
# Fixup row number to remove gaps
|
||||||
_playlist_check_playlist(session, playlist_id, fix=True)
|
_playlist_check_playlist(session, playlist_id, fix=True)
|
||||||
session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
# @log_call
|
# @log_call
|
||||||
@ -919,11 +983,9 @@ def playlist_save_tabs(playlist_id_to_tab: dict[int, int]) -> None:
|
|||||||
.where(Playlists.id.in_(playlist_id_to_tab.keys()))
|
.where(Playlists.id.in_(playlist_id_to_tab.keys()))
|
||||||
.values(tab=None)
|
.values(tab=None)
|
||||||
)
|
)
|
||||||
for (playlist_id, tab) in playlist_id_to_tab.items():
|
for playlist_id, tab in playlist_id_to_tab.items():
|
||||||
session.execute(
|
session.execute(
|
||||||
update(Playlists)
|
update(Playlists).where(Playlists.id == playlist_id).values(tab=tab)
|
||||||
.where(Playlists.id == playlist_id)
|
|
||||||
.values(tab=tab)
|
|
||||||
)
|
)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
@ -943,6 +1005,7 @@ def playlist_update_template_favourite(template_id: int, favourite: bool) -> Non
|
|||||||
|
|
||||||
# Playlist Rows
|
# Playlist Rows
|
||||||
|
|
||||||
|
|
||||||
# @log_call
|
# @log_call
|
||||||
def playlistrow_by_id(playlistrow_id: int) -> PlaylistRowDTO | None:
|
def playlistrow_by_id(playlistrow_id: int) -> PlaylistRowDTO | None:
|
||||||
"""
|
"""
|
||||||
@ -951,7 +1014,9 @@ def playlistrow_by_id(playlistrow_id: int) -> PlaylistRowDTO | None:
|
|||||||
|
|
||||||
with db.Session() as session:
|
with db.Session() as session:
|
||||||
record = (
|
record = (
|
||||||
session.execute(select(PlaylistRows).where(PlaylistRows.id == playlistrow_id))
|
session.execute(
|
||||||
|
select(PlaylistRows).where(PlaylistRows.id == playlistrow_id)
|
||||||
|
)
|
||||||
.scalars()
|
.scalars()
|
||||||
.one_or_none()
|
.one_or_none()
|
||||||
)
|
)
|
||||||
@ -977,9 +1042,7 @@ def playlistrow_by_id(playlistrow_id: int) -> PlaylistRowDTO | None:
|
|||||||
def playlistrows_by_playlist(
|
def playlistrows_by_playlist(
|
||||||
playlist_id: int, check_playlist_itegrity: bool = True
|
playlist_id: int, check_playlist_itegrity: bool = True
|
||||||
) -> list[PlaylistRowDTO]:
|
) -> list[PlaylistRowDTO]:
|
||||||
|
|
||||||
with db.Session() as session:
|
with db.Session() as session:
|
||||||
|
|
||||||
# TODO: would be good to be confident at removing this
|
# TODO: would be good to be confident at removing this
|
||||||
if check_playlist_itegrity:
|
if check_playlist_itegrity:
|
||||||
_playlist_check_playlist(
|
_playlist_check_playlist(
|
||||||
@ -994,7 +1057,6 @@ def playlistrows_by_playlist(
|
|||||||
|
|
||||||
dto_list = []
|
dto_list = []
|
||||||
for record in records:
|
for record in records:
|
||||||
|
|
||||||
track = None
|
track = None
|
||||||
if record.track_id:
|
if record.track_id:
|
||||||
track = track_by_id(record.track_id)
|
track = track_by_id(record.track_id)
|
||||||
@ -1040,7 +1102,9 @@ def playlistrow_played(playlistrow_id: int, status: bool) -> None:
|
|||||||
|
|
||||||
with db.Session() as session:
|
with db.Session() as session:
|
||||||
session.execute(
|
session.execute(
|
||||||
update(PlaylistRows).where(PlaylistRows.id == playlistrow_id).values(played=status)
|
update(PlaylistRows)
|
||||||
|
.where(PlaylistRows.id == playlistrow_id)
|
||||||
|
.values(played=status)
|
||||||
)
|
)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
@ -1096,10 +1160,7 @@ def playdates_between_dates(
|
|||||||
Playdates.lastplayed,
|
Playdates.lastplayed,
|
||||||
Playdates.track_id,
|
Playdates.track_id,
|
||||||
Playdates.track,
|
Playdates.track,
|
||||||
).where(
|
).where(Playdates.lastplayed >= start, Playdates.lastplayed <= end)
|
||||||
Playdates.lastplayed >= start,
|
|
||||||
Playdates.lastplayed <= end
|
|
||||||
)
|
|
||||||
|
|
||||||
results: list[PlaydatesDTO] = []
|
results: list[PlaydatesDTO] = []
|
||||||
|
|
||||||
@ -1137,10 +1198,7 @@ def _queries_where(
|
|||||||
results: list[QueryDTO] = []
|
results: list[QueryDTO] = []
|
||||||
|
|
||||||
with db.Session() as session:
|
with db.Session() as session:
|
||||||
records = session.scalars(
|
records = session.scalars(select(Queries).where(query)).all()
|
||||||
select(Queries)
|
|
||||||
.where(query)
|
|
||||||
).all()
|
|
||||||
for record in records:
|
for record in records:
|
||||||
dto = QueryDTO(
|
dto = QueryDTO(
|
||||||
favourite=record.favourite,
|
favourite=record.favourite,
|
||||||
@ -1273,4 +1331,3 @@ def db_name_get() -> str:
|
|||||||
dbname = session.bind.engine.url.database
|
dbname = session.bind.engine.url.database
|
||||||
return dbname
|
return dbname
|
||||||
return Config.DB_NOT_FOUND
|
return Config.DB_NOT_FOUND
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ disable_existing_loggers: True
|
|||||||
formatters:
|
formatters:
|
||||||
colored:
|
colored:
|
||||||
(): colorlog.ColoredFormatter
|
(): colorlog.ColoredFormatter
|
||||||
format: "%(log_color)s[%(asctime)s] %(filename)s.%(funcName)s:%(lineno)s %(blue)s%(message)s"
|
format: "%(log_color)s[%(asctime)s] %(filename)s.%(funcName)s:%(lineno)s %(light_blue)s%(message)s"
|
||||||
datefmt: "%H:%M:%S"
|
datefmt: "%H:%M:%S"
|
||||||
syslog:
|
syslog:
|
||||||
format: "[%(name)s] %(filename)s:%(lineno)s %(leveltag)s: %(message)s"
|
format: "[%(name)s] %(filename)s:%(lineno)s %(leveltag)s: %(message)s"
|
||||||
@ -25,6 +25,7 @@ filters:
|
|||||||
musicmuster:
|
musicmuster:
|
||||||
- update_clocks
|
- update_clocks
|
||||||
- play_next
|
- play_next
|
||||||
|
- show_signal
|
||||||
|
|
||||||
handlers:
|
handlers:
|
||||||
stderr:
|
stderr:
|
||||||
|
|||||||
@ -34,14 +34,6 @@ import dbtables
|
|||||||
from log import log
|
from log import log
|
||||||
|
|
||||||
|
|
||||||
# Establish database connection
|
|
||||||
DATABASE_URL = os.environ.get("DATABASE_URL")
|
|
||||||
if DATABASE_URL is None:
|
|
||||||
raise ValueError("DATABASE_URL is undefined")
|
|
||||||
if "unittest" in sys.modules and "sqlite" not in DATABASE_URL:
|
|
||||||
raise ValueError("Unit tests running on non-Sqlite database")
|
|
||||||
db = DatabaseManager.get_instance(DATABASE_URL, engine_options=Config.ENGINE_OPTIONS).db
|
|
||||||
|
|
||||||
# Configure the cache region
|
# Configure the cache region
|
||||||
cache_region = make_region().configure(
|
cache_region = make_region().configure(
|
||||||
'dogpile.cache.memory', # Use in-memory caching for now (switch to Redis if needed)
|
'dogpile.cache.memory', # Use in-memory caching for now (switch to Redis if needed)
|
||||||
|
|||||||
@ -16,44 +16,11 @@ from PyQt6.QtCore import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
# App imports
|
# App imports
|
||||||
from classes import singleton
|
from classes import MusicMusterSignals, singleton
|
||||||
from config import Config
|
from config import Config
|
||||||
import helpers
|
import helpers
|
||||||
from log import log
|
from log import log
|
||||||
|
|
||||||
# Define the VLC callback function type
|
|
||||||
# import ctypes
|
|
||||||
# import platform
|
|
||||||
# VLC logging is very noisy so comment out unless needed
|
|
||||||
# VLC_LOG_CB = ctypes.CFUNCTYPE(
|
|
||||||
# None,
|
|
||||||
# ctypes.c_void_p,
|
|
||||||
# ctypes.c_int,
|
|
||||||
# ctypes.c_void_p,
|
|
||||||
# ctypes.c_char_p,
|
|
||||||
# ctypes.c_void_p,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Determine the correct C library for vsnprintf based on the platform
|
|
||||||
# if platform.system() == "Windows":
|
|
||||||
# libc = ctypes.CDLL("msvcrt")
|
|
||||||
# elif platform.system() == "Linux":
|
|
||||||
# libc = ctypes.CDLL("libc.so.6")
|
|
||||||
# elif platform.system() == "Darwin": # macOS
|
|
||||||
# libc = ctypes.CDLL("libc.dylib")
|
|
||||||
# else:
|
|
||||||
# raise OSError("Unsupported operating system")
|
|
||||||
|
|
||||||
# # Define the vsnprintf function
|
|
||||||
# libc.vsnprintf.argtypes = [
|
|
||||||
# ctypes.c_char_p,
|
|
||||||
# ctypes.c_size_t,
|
|
||||||
# ctypes.c_char_p,
|
|
||||||
# ctypes.c_void_p,
|
|
||||||
# ]
|
|
||||||
# libc.vsnprintf.restype = ctypes.c_int
|
|
||||||
|
|
||||||
|
|
||||||
class _FadeTrack(QThread):
|
class _FadeTrack(QThread):
|
||||||
finished = pyqtSignal()
|
finished = pyqtSignal()
|
||||||
|
|
||||||
@ -113,32 +80,7 @@ class Music:
|
|||||||
self.player: vlc.MediaPlayer | None = None
|
self.player: vlc.MediaPlayer | None = None
|
||||||
self.max_volume: int = Config.VLC_VOLUME_DEFAULT
|
self.max_volume: int = Config.VLC_VOLUME_DEFAULT
|
||||||
self.start_dt: dt.datetime | None = None
|
self.start_dt: dt.datetime | None = None
|
||||||
|
self.signals = MusicMusterSignals()
|
||||||
# Set up logging
|
|
||||||
# self._set_vlc_log()
|
|
||||||
|
|
||||||
# VLC logging very noisy so comment out unless needed
|
|
||||||
# @VLC_LOG_CB
|
|
||||||
# def log_callback(data, level, ctx, fmt, args):
|
|
||||||
# try:
|
|
||||||
# # Create a ctypes string buffer to hold the formatted message
|
|
||||||
# buf = ctypes.create_string_buffer(1024)
|
|
||||||
|
|
||||||
# # Use vsnprintf to format the string with the va_list
|
|
||||||
# libc.vsnprintf(buf, len(buf), fmt, args)
|
|
||||||
|
|
||||||
# # Decode the formatted message
|
|
||||||
# message = buf.value.decode("utf-8", errors="replace")
|
|
||||||
# log.debug("VLC: " + message)
|
|
||||||
# except Exception as e:
|
|
||||||
# log.error(f"Error in VLC log callback: {e}")
|
|
||||||
|
|
||||||
# def _set_vlc_log(self):
|
|
||||||
# try:
|
|
||||||
# vlc.libvlc_log_set(vlc_instance, self.log_callback, None)
|
|
||||||
# log.debug("VLC logging set up successfully")
|
|
||||||
# except Exception as e:
|
|
||||||
# log.error(f"Failed to set up VLC logging: {e}")
|
|
||||||
|
|
||||||
def fade(self, fade_seconds: int) -> None:
|
def fade(self, fade_seconds: int) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
# Standard library imports
|
# Standard library imports
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
from functools import partial
|
||||||
from slugify import slugify # type: ignore
|
from slugify import slugify # type: ignore
|
||||||
from typing import Callable
|
from typing import Any, Callable
|
||||||
import argparse
|
import argparse
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
@ -93,6 +94,66 @@ import ds
|
|||||||
import helpers
|
import helpers
|
||||||
|
|
||||||
|
|
||||||
|
class SignalMonitor:
|
||||||
|
def __init__(self):
|
||||||
|
self.signals = MusicMusterSignals()
|
||||||
|
|
||||||
|
self.signals.enable_escape_signal.connect(
|
||||||
|
partial(self.show_signal, "enable_escape_signal ")
|
||||||
|
)
|
||||||
|
self.signals.next_track_changed_signal.connect(
|
||||||
|
partial(self.show_signal, "next_track_changed_signal ")
|
||||||
|
)
|
||||||
|
self.signals.resize_rows_signal.connect(
|
||||||
|
partial(self.show_signal, "resize_rows_signal ")
|
||||||
|
)
|
||||||
|
self.signals.search_songfacts_signal.connect(
|
||||||
|
partial(self.show_signal, "search_songfacts_signal ")
|
||||||
|
)
|
||||||
|
self.signals.search_wikipedia_signal.connect(
|
||||||
|
partial(self.show_signal, "search_wikipedia_signal ")
|
||||||
|
)
|
||||||
|
self.signals.show_warning_signal.connect(
|
||||||
|
partial(self.show_signal, "show_warning_signal ")
|
||||||
|
)
|
||||||
|
self.signals.signal_add_track_to_header.connect(
|
||||||
|
partial(self.show_signal, "signal_add_track_to_header ")
|
||||||
|
)
|
||||||
|
self.signals.signal_begin_insert_rows.connect(
|
||||||
|
partial(self.show_signal, "signal_begin_insert_rows ")
|
||||||
|
)
|
||||||
|
self.signals.signal_end_insert_rows.connect(
|
||||||
|
partial(self.show_signal, "signal_end_insert_rows ")
|
||||||
|
)
|
||||||
|
self.signals.signal_insert_track.connect(
|
||||||
|
partial(self.show_signal, "signal_insert_track ")
|
||||||
|
)
|
||||||
|
self.signals.signal_playlist_selected_rows.connect(
|
||||||
|
partial(self.show_signal, "signal_playlist_selected_rows ")
|
||||||
|
)
|
||||||
|
self.signals.signal_set_next_row.connect(
|
||||||
|
partial(self.show_signal, "signal_set_next_row ")
|
||||||
|
)
|
||||||
|
self.signals.signal_set_next_track.connect(
|
||||||
|
partial(self.show_signal, "signal_set_next_track ")
|
||||||
|
)
|
||||||
|
self.signals.signal_track_started.connect(
|
||||||
|
partial(self.show_signal, "signal_track_started ")
|
||||||
|
)
|
||||||
|
# self.signals.span_cells_signal.connect(
|
||||||
|
# partial(self.show_signal, "span_cells_signal ")
|
||||||
|
# )
|
||||||
|
self.signals.status_message_signal.connect(
|
||||||
|
partial(self.show_signal, "status_message_signal ")
|
||||||
|
)
|
||||||
|
self.signals.track_ended_signal.connect(
|
||||||
|
partial(self.show_signal, "track_ended_signal ")
|
||||||
|
)
|
||||||
|
|
||||||
|
def show_signal(self, name: str, *args: Any) -> None:
|
||||||
|
log.debug(f"{name=}, args={args}")
|
||||||
|
|
||||||
|
|
||||||
class Current:
|
class Current:
|
||||||
base_model: PlaylistModel
|
base_model: PlaylistModel
|
||||||
proxy_model: PlaylistProxyModel
|
proxy_model: PlaylistProxyModel
|
||||||
@ -770,6 +831,7 @@ class QueryDialog(QDialog):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.playlist_id = playlist_id
|
self.playlist_id = playlist_id
|
||||||
self.default = default
|
self.default = default
|
||||||
|
self.signals = MusicMusterSignals()
|
||||||
|
|
||||||
# Build a list of (query-name, playlist-id) tuples
|
# Build a list of (query-name, playlist-id) tuples
|
||||||
self.selected_tracks: list[int] = []
|
self.selected_tracks: list[int] = []
|
||||||
@ -862,9 +924,9 @@ class QueryDialog(QDialog):
|
|||||||
|
|
||||||
# new_row_number = self.current_row_or_end()
|
# new_row_number = self.current_row_or_end()
|
||||||
# base_model = self.current.base_model
|
# base_model = self.current.base_model
|
||||||
for track_id in self.query_dialog.selected_tracks:
|
for track_id in self.selected_tracks:
|
||||||
insert_track_data = InsertTrack(self.playlist_id, track_id, note="")
|
insert_track_data = InsertTrack(self.playlist_id, track_id, note="")
|
||||||
self.signals.signal_insert_track.emit(InsertTrack(insert_track_data))
|
self.signals.signal_insert_track.emit(insert_track_data)
|
||||||
|
|
||||||
self.accept()
|
self.accept()
|
||||||
|
|
||||||
@ -1180,6 +1242,9 @@ class Window(QMainWindow):
|
|||||||
self.action_quicklog = QShortcut(QKeySequence("Ctrl+L"), self)
|
self.action_quicklog = QShortcut(QKeySequence("Ctrl+L"), self)
|
||||||
self.action_quicklog.activated.connect(self.quicklog)
|
self.action_quicklog.activated.connect(self.quicklog)
|
||||||
|
|
||||||
|
# Optionally print signals
|
||||||
|
self.signal_monitor = SignalMonitor()
|
||||||
|
|
||||||
# Load playlists
|
# Load playlists
|
||||||
self.load_last_playlists()
|
self.load_last_playlists()
|
||||||
|
|
||||||
@ -1519,9 +1584,7 @@ class Window(QMainWindow):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def solicit_template_to_use(
|
def solicit_template_to_use(self, template_prompt: str | None = None) -> int | None:
|
||||||
self, template_prompt: str | None = None
|
|
||||||
) -> int | None:
|
|
||||||
"""
|
"""
|
||||||
Have user select a template. Return the template.id, or None if they cancel.
|
Have user select a template. Return the template.id, or None if they cancel.
|
||||||
template_id of zero means don't use a template.
|
template_id of zero means don't use a template.
|
||||||
@ -1881,7 +1944,7 @@ class Window(QMainWindow):
|
|||||||
InsertTrack(
|
InsertTrack(
|
||||||
playlist_id=self.current.base_model.playlist_id,
|
playlist_id=self.current.base_model.playlist_id,
|
||||||
track_id=None,
|
track_id=None,
|
||||||
note=dlg.textValue()
|
note=dlg.textValue(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1936,8 +1999,7 @@ class Window(QMainWindow):
|
|||||||
# Save the selected PlaylistRows items ready for a later
|
# Save the selected PlaylistRows items ready for a later
|
||||||
# paste
|
# paste
|
||||||
self.move_source = MoveSource(
|
self.move_source = MoveSource(
|
||||||
model=self.current.base_model,
|
model=self.current.base_model, rows=self.current.selected_row_numbers
|
||||||
rows=self.current.selected_row_numbers
|
|
||||||
)
|
)
|
||||||
|
|
||||||
log.debug(f"mark_rows_for_moving(): {self.move_source=}")
|
log.debug(f"mark_rows_for_moving(): {self.move_source=}")
|
||||||
@ -2054,9 +2116,7 @@ class Window(QMainWindow):
|
|||||||
to_playlist_model.set_next_row(to_row)
|
to_playlist_model.set_next_row(to_row)
|
||||||
|
|
||||||
# @log_call
|
# @log_call
|
||||||
def play_next(
|
def play_next(self, position: float | None = None, checked: bool = False) -> None:
|
||||||
self, position: float | None = None, checked: bool = False
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Play next track, optionally from passed position.
|
Play next track, optionally from passed position.
|
||||||
|
|
||||||
|
|||||||
@ -717,12 +717,11 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
track_id=row_data.track_id,
|
track_id=row_data.track_id,
|
||||||
note=row_data.note,
|
note=row_data.note,
|
||||||
)
|
)
|
||||||
|
super().endInsertRows()
|
||||||
|
|
||||||
# Need to refresh self.playlist_rows because row numbers will have
|
# Need to refresh self.playlist_rows because row numbers will have
|
||||||
# changed
|
# changed
|
||||||
self.refresh_data()
|
self.refresh_data()
|
||||||
|
|
||||||
super().endInsertRows()
|
|
||||||
|
|
||||||
self.signals.resize_rows_signal.emit(self.playlist_id)
|
self.signals.resize_rows_signal.emit(self.playlist_id)
|
||||||
self.track_sequence.update()
|
self.track_sequence.update()
|
||||||
self.update_track_times()
|
self.update_track_times()
|
||||||
|
|||||||
@ -19,10 +19,10 @@ import pyqtgraph as pg # type: ignore
|
|||||||
# App imports
|
# App imports
|
||||||
from classes import ApplicationError, MusicMusterSignals, PlaylistRowDTO, singleton
|
from classes import ApplicationError, MusicMusterSignals, PlaylistRowDTO, singleton
|
||||||
from config import Config
|
from config import Config
|
||||||
import helpers
|
|
||||||
from log import log
|
from log import log
|
||||||
from music_manager import Music
|
from music_manager import Music
|
||||||
import ds
|
import ds
|
||||||
|
import helpers
|
||||||
|
|
||||||
|
|
||||||
class PlaylistRow:
|
class PlaylistRow:
|
||||||
@ -32,7 +32,8 @@ class PlaylistRow:
|
|||||||
|
|
||||||
def __init__(self, dto: PlaylistRowDTO) -> None:
|
def __init__(self, dto: PlaylistRowDTO) -> None:
|
||||||
"""
|
"""
|
||||||
The dto object will include a Tracks object if this row has a track.
|
The dto object will include row information plus a Tracks object
|
||||||
|
if this row has a track.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.dto = dto
|
self.dto = dto
|
||||||
@ -64,7 +65,7 @@ class PlaylistRow:
|
|||||||
|
|
||||||
# Expose TrackDTO fields as properties
|
# Expose TrackDTO fields as properties
|
||||||
@property
|
@property
|
||||||
def artist(self):
|
def artist(self) -> str:
|
||||||
if self.dto.track:
|
if self.dto.track:
|
||||||
return self.dto.track.artist
|
return self.dto.track.artist
|
||||||
else:
|
else:
|
||||||
@ -79,28 +80,28 @@ class PlaylistRow:
|
|||||||
ds.track_update(self.track_id, dict(artist=str(artist)))
|
ds.track_update(self.track_id, dict(artist=str(artist)))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bitrate(self):
|
def bitrate(self) -> int:
|
||||||
if self.dto.track:
|
if self.dto.track:
|
||||||
return self.dto.track.bitrate
|
return self.dto.track.bitrate
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def duration(self):
|
def duration(self) -> int:
|
||||||
if self.dto.track:
|
if self.dto.track:
|
||||||
return self.dto.track.duration
|
return self.dto.track.duration
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fade_at(self):
|
def fade_at(self) -> int:
|
||||||
if self.dto.track:
|
if self.dto.track:
|
||||||
return self.dto.track.fade_at
|
return self.dto.track.fade_at
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def intro(self):
|
def intro(self) -> int:
|
||||||
if self.dto.track:
|
if self.dto.track:
|
||||||
return self.dto.track.intro
|
return self.dto.track.intro
|
||||||
else:
|
else:
|
||||||
@ -115,35 +116,35 @@ class PlaylistRow:
|
|||||||
ds.track_update(self.track_id, dict(intro=str(intro)))
|
ds.track_update(self.track_id, dict(intro=str(intro)))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def lastplayed(self):
|
def lastplayed(self) -> dt.datetime | None:
|
||||||
if self.dto.track:
|
if self.dto.track:
|
||||||
return self.dto.track.lastplayed
|
return self.dto.track.lastplayed
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self):
|
def path(self) -> str:
|
||||||
if self.dto.track:
|
if self.dto.track:
|
||||||
return self.dto.track.path
|
return self.dto.track.path
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def silence_at(self):
|
def silence_at(self) -> int:
|
||||||
if self.dto.track:
|
if self.dto.track:
|
||||||
return self.dto.track.silence_at
|
return self.dto.track.silence_at
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def start_gap(self):
|
def start_gap(self) -> int:
|
||||||
if self.dto.track:
|
if self.dto.track:
|
||||||
return self.dto.track.start_gap
|
return self.dto.track.start_gap
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def title(self):
|
def title(self) -> str:
|
||||||
if self.dto.track:
|
if self.dto.track:
|
||||||
return self.dto.track.title
|
return self.dto.track.title
|
||||||
else:
|
else:
|
||||||
@ -158,7 +159,7 @@ class PlaylistRow:
|
|||||||
ds.track_update(self.track_id, dict(title=str(title)))
|
ds.track_update(self.track_id, dict(title=str(title)))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def track_id(self):
|
def track_id(self) -> int:
|
||||||
if self.dto.track:
|
if self.dto.track:
|
||||||
return self.dto.track.track_id
|
return self.dto.track.track_id
|
||||||
else:
|
else:
|
||||||
@ -183,7 +184,7 @@ class PlaylistRow:
|
|||||||
|
|
||||||
# Expose PlaylistRowDTO fields as properties
|
# Expose PlaylistRowDTO fields as properties
|
||||||
@property
|
@property
|
||||||
def note(self):
|
def note(self) -> str:
|
||||||
return self.dto.note
|
return self.dto.note
|
||||||
|
|
||||||
@note.setter
|
@note.setter
|
||||||
@ -192,7 +193,7 @@ class PlaylistRow:
|
|||||||
ds.playlistrow_update_note(self.playlistrow_id, str(note))
|
ds.playlistrow_update_note(self.playlistrow_id, str(note))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def played(self):
|
def played(self) -> bool:
|
||||||
return self.dto.played
|
return self.dto.played
|
||||||
|
|
||||||
@played.setter
|
@played.setter
|
||||||
@ -201,15 +202,15 @@ class PlaylistRow:
|
|||||||
ds.playlistrow_played(self.playlistrow_id, value)
|
ds.playlistrow_played(self.playlistrow_id, value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def playlist_id(self):
|
def playlist_id(self) -> int:
|
||||||
return self.dto.playlist_id
|
return self.dto.playlist_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def playlistrow_id(self):
|
def playlistrow_id(self) -> int:
|
||||||
return self.dto.playlistrow_id
|
return self.dto.playlistrow_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def row_number(self):
|
def row_number(self) -> int:
|
||||||
return self.dto.row_number
|
return self.dto.row_number
|
||||||
|
|
||||||
@row_number.setter
|
@row_number.setter
|
||||||
|
|||||||
@ -165,7 +165,7 @@ class MyTestCase(unittest.TestCase):
|
|||||||
new_order = []
|
new_order = []
|
||||||
for row in ds.playlistrows_by_playlist(playlist.playlist_id):
|
for row in ds.playlistrows_by_playlist(playlist.playlist_id):
|
||||||
new_order.append(int(row.note))
|
new_order.append(int(row.note))
|
||||||
assert new_order == [0, 1, 2, 4, 5, 3, 6, 7, 8, 9]
|
assert new_order == [0, 1, 2, 4, 3, 5, 6, 7, 8, 9]
|
||||||
|
|
||||||
def test_move_rows_test2(self):
|
def test_move_rows_test2(self):
|
||||||
# move row 4 to row 3
|
# move row 4 to row 3
|
||||||
@ -207,7 +207,7 @@ class MyTestCase(unittest.TestCase):
|
|||||||
new_order = []
|
new_order = []
|
||||||
for row in ds.playlistrows_by_playlist(playlist.playlist_id):
|
for row in ds.playlistrows_by_playlist(playlist.playlist_id):
|
||||||
new_order.append(int(row.note))
|
new_order.append(int(row.note))
|
||||||
assert new_order == [0, 2, 3, 6, 7, 8, 1, 4, 5, 10, 9]
|
assert new_order == [0, 2, 3, 6, 7, 1, 4, 5, 10, 8, 9]
|
||||||
|
|
||||||
def test_move_rows_test5(self):
|
def test_move_rows_test5(self):
|
||||||
# move rows [3, 6] → 5
|
# move rows [3, 6] → 5
|
||||||
@ -221,7 +221,7 @@ class MyTestCase(unittest.TestCase):
|
|||||||
new_order = []
|
new_order = []
|
||||||
for row in ds.playlistrows_by_playlist(playlist.playlist_id):
|
for row in ds.playlistrows_by_playlist(playlist.playlist_id):
|
||||||
new_order.append(int(row.note))
|
new_order.append(int(row.note))
|
||||||
assert new_order == [0, 1, 2, 4, 5, 3, 6, 7, 8, 9, 10]
|
assert new_order == [0, 1, 2, 4, 3, 6, 5, 7, 8, 9, 10]
|
||||||
|
|
||||||
def test_move_rows_test6(self):
|
def test_move_rows_test6(self):
|
||||||
# move rows [3, 5, 6] → 8
|
# move rows [3, 5, 6] → 8
|
||||||
@ -235,7 +235,7 @@ class MyTestCase(unittest.TestCase):
|
|||||||
new_order = []
|
new_order = []
|
||||||
for row in ds.playlistrows_by_playlist(playlist.playlist_id):
|
for row in ds.playlistrows_by_playlist(playlist.playlist_id):
|
||||||
new_order.append(int(row.note))
|
new_order.append(int(row.note))
|
||||||
assert new_order == [0, 1, 2, 4, 7, 8, 9, 10, 3, 5, 6]
|
assert new_order == [0, 1, 2, 4, 7, 3, 5, 6, 8, 9, 10]
|
||||||
|
|
||||||
def test_move_rows_test7(self):
|
def test_move_rows_test7(self):
|
||||||
# move rows [7, 8, 10] → 5
|
# move rows [7, 8, 10] → 5
|
||||||
@ -258,13 +258,13 @@ class MyTestCase(unittest.TestCase):
|
|||||||
number_of_rows = 11
|
number_of_rows = 11
|
||||||
(playlist, model) = self.create_rows("test_move_rows_test8", number_of_rows)
|
(playlist, model) = self.create_rows("test_move_rows_test8", number_of_rows)
|
||||||
|
|
||||||
ds.playlist_move_rows([0, 1, 2, 3], playlist.playlist_id, 0)
|
ds.playlist_move_rows([1, 2, 3], playlist.playlist_id, 0)
|
||||||
|
|
||||||
# Check we have all rows and plr_rownums are correct
|
# Check we have all rows and plr_rownums are correct
|
||||||
new_order = []
|
new_order = []
|
||||||
for row in ds.playlistrows_by_playlist(playlist.playlist_id):
|
for row in ds.playlistrows_by_playlist(playlist.playlist_id):
|
||||||
new_order.append(int(row.note))
|
new_order.append(int(row.note))
|
||||||
assert new_order == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
assert new_order == [1, 2, 3, 0, 4, 5, 6, 7, 8, 9, 10]
|
||||||
|
|
||||||
def test_move_rows_to_playlist(self):
|
def test_move_rows_to_playlist(self):
|
||||||
number_of_rows = 11
|
number_of_rows = 11
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user