WIP: remove session from playlistmodel

This commit is contained in:
Keith Edmunds 2025-04-04 18:54:46 +01:00
parent e39518e5ee
commit c182a69a5d
4 changed files with 175 additions and 146 deletions

View File

@ -44,11 +44,8 @@ from helpers import (
get_embedded_time,
get_relative_date,
ms_to_mmss,
remove_substring_case_insensitive,
set_track_metadata,
)
from log import log, log_call
from models import db, NoteColours, Playdates, PlaylistRows, Tracks
from playlistrow import PlaylistRow, TrackSequence
import repository
@ -61,6 +58,9 @@ class PlaylistModel(QAbstractTableModel):
"""
The Playlist Model
Cache the database info in self.playlist_rows, a dictionary of
PlaylistRow objects indexed by row_number.
Update strategy: update the database and then refresh the
row-indexed cached copy (self.playlist_rows). Do not edit
self.playlist_rows directly because keeping it and the
@ -943,8 +943,6 @@ class PlaylistModel(QAbstractTableModel):
playlist_row.note = note
self.refresh_row(existing_plr.row_number)
# Carry out the move outside of the session context to ensure
# database updated with any note change
self.move_rows([existing_plr.row_number], new_row_number)
self.signals.resize_rows_signal.emit(self.playlist_id)
@ -1067,13 +1065,9 @@ class PlaylistModel(QAbstractTableModel):
Rescan track at passed row number
"""
track_id = self.playlist_rows[row_number].track_id
if track_id:
with db.Session() as session:
track = session.get(Tracks, track_id)
set_track_metadata(track)
self.refresh_row(row_number)
self.update_track_times()
track = self.playlist_rows[row_number]
_ = repository.update_track(track.path, track.track_id)
roles = [
Qt.ItemDataRole.BackgroundRole,
Qt.ItemDataRole.DisplayRole,
@ -1081,7 +1075,6 @@ class PlaylistModel(QAbstractTableModel):
# only invalidate required roles
self.invalidate_row(row_number, roles)
self.signals.resize_rows_signal.emit(self.playlist_id)
session.commit()
@log_call
def reset_track_sequence_row_numbers(self) -> None:
@ -1113,21 +1106,8 @@ class PlaylistModel(QAbstractTableModel):
):
return
with db.Session() as session:
for row_number in row_numbers:
playlist_row = session.get(
PlaylistRows, self.playlist_rows[row_number].playlistrow_id
)
if playlist_row.track_id:
playlist_row.note = ""
# We can't use refresh_data() because its
# optimisations mean it won't update comments in
# self.playlist_rows
# The "correct" approach would be to re-read from the
# database but we optimise here by simply updating
# self.playlist_rows directly.
self.playlist_rows[row_number].note = ""
session.commit()
repository.remove_comments(self.playlist_id, row_numbers)
# only invalidate required roles
roles = [
Qt.ItemDataRole.BackgroundRole,
@ -1185,31 +1165,7 @@ class PlaylistModel(QAbstractTableModel):
header_text = header_text[0:-1]
# Parse passed header text and remove the first colour match string
with db.Session() as session:
for rec in NoteColours.get_all(session):
if not rec.strip_substring:
continue
if rec.is_regex:
flags = re.UNICODE
if not rec.is_casesensitive:
flags |= re.IGNORECASE
p = re.compile(rec.substring, flags)
if p.match(header_text):
header_text = re.sub(p, "", header_text)
break
else:
if rec.is_casesensitive:
if rec.substring.lower() in header_text.lower():
header_text = remove_substring_case_insensitive(
header_text, rec.substring
)
break
else:
if rec.substring in header_text:
header_text = header_text.replace(rec.substring, "")
break
return header_text
return repository.remove_colour_substring(header_text)
def rowCount(self, index: QModelIndex = QModelIndex()) -> int:
"""Standard function for view"""
@ -1357,6 +1313,7 @@ class PlaylistModel(QAbstractTableModel):
self.signals.next_track_changed_signal.emit()
self.update_track_times()
@log_call
def setData(
self,
index: QModelIndex,
@ -1372,42 +1329,19 @@ class PlaylistModel(QAbstractTableModel):
row_number = index.row()
column = index.column()
plr = self.playlist_rows[row_number]
with db.Session() as session:
playlist_row = session.get(
PlaylistRows, self.playlist_rows[row_number].playlistrow_id
)
if not playlist_row:
log.error(
f"{self}: Error saving data: {row_number=}, {column=}, {value=}"
)
return False
if playlist_row.track_id:
if column in [Col.TITLE.value, Col.ARTIST.value, Col.INTRO.value]:
track = session.get(Tracks, playlist_row.track_id)
if not track:
log.error(f"{self}: Error retreiving track: {playlist_row=}")
return False
if column == Col.TITLE.value:
track.title = str(value)
plr.title = str(value)
elif column == Col.ARTIST.value:
track.artist = str(value)
plr.artist = str(value)
elif column == Col.INTRO.value:
track.intro = int(round(float(value), 1) * 1000)
else:
log.error(f"{self}: Error updating track: {column=}, {value=}")
return False
plr.intro = int(round(float(value), 1) * 1000)
elif column == Col.NOTE.value:
playlist_row.note = str(value)
plr.note = str(value)
else:
# This is a header row
if column == HEADER_NOTES_COLUMN:
playlist_row.note = str(value)
raise ApplicationError(f"setData called with unexpected column ({column=})")
# commit changes before refreshing data
session.commit()
self.refresh_row(row_number)
self.dataChanged.emit(index, index, [Qt.ItemDataRole.DisplayRole, role])
@ -1509,17 +1443,12 @@ class PlaylistModel(QAbstractTableModel):
if column != Col.LAST_PLAYED.value:
return ""
with db.Session() as session:
track_id = self.playlist_rows[row].track_id
if not track_id:
return ""
playdates = Playdates.last_playdates(session, track_id)
return "<br>".join(
[
a.lastplayed.strftime(Config.LAST_PLAYED_TOOLTIP_DATE_FORMAT)
for a in playdates
]
)
return repository.get_last_played_dates(track_id)
@log_call
def update_or_insert(self, track_id: int, row_number: int) -> None:

View File

@ -64,6 +64,10 @@ class PlaylistRow:
def artist(self):
return self.dto.artist
@artist.setter
def artist(self, value: str) -> None:
print(f"set artist attribute for {self=}, {value=}")
@property
def bitrate(self):
return self.dto.bitrate
@ -80,6 +84,10 @@ class PlaylistRow:
def intro(self):
return self.dto.intro
@intro.setter
def intro(self, value: int) -> None:
print(f"set intro attribute for {self=}, {value=}")
@property
def lastplayed(self):
return self.dto.lastplayed
@ -100,6 +108,10 @@ class PlaylistRow:
def title(self):
return self.dto.title
@title.setter
def title(self, value: str) -> None:
print(f"set title attribute for {self=}, {value=}")
@property
def track_id(self):
return self.dto.track_id

View File

@ -51,7 +51,6 @@ from helpers import (
show_warning,
)
from log import log, log_call
from models import db, Settings
from playlistrow import TrackSequence
from playlistmodel import PlaylistModel, PlaylistProxyModel
import repository

View File

@ -19,7 +19,7 @@ from classes import ApplicationError, PlaylistRowDTO
from classes import PlaylistDTO, TrackDTO
from config import Config
import helpers
from log import log
from log import log, log_call
from models import (
db,
NoteColours,
@ -32,17 +32,11 @@ from models import (
# Notecolour functions
def get_colour(text: str, foreground: bool = False) -> str:
def _get_colour_record(text: str) -> tuple[NoteColours | None, str]:
"""
Parse text and return background (foreground if foreground==True)
colour string if matched, else None
Parse text and return first matching colour record or None
"""
if not text:
return ""
match = False
with db.Session() as session:
for rec in NoteColours.get_all(session):
if rec.is_regex:
@ -51,21 +45,49 @@ def get_colour(text: str, foreground: bool = False) -> str:
flags |= re.IGNORECASE
p = re.compile(rec.substring, flags)
if p.match(text):
match = True
if rec.strip_substring:
return_text = re.sub(p, "", text)
else:
return_text = text
return (rec, return_text)
else:
if rec.is_casesensitive:
if rec.substring in text:
match = True
return_text = text.replace(rec.substring, "")
return (rec, return_text)
else:
if rec.substring.lower() in text.lower():
match = True
return_text = helpers.remove_substring_case_insensitive(
text, rec.substring
)
return (rec, return_text)
if match:
if foreground:
return (None, text)
def get_colour(text: str, foreground: bool = False) -> str:
"""
Parse text and return background (foreground if foreground==True)
colour string if matched, else None
"""
(rec, _) = _get_colour_record(text)
if rec is None:
return ""
elif foreground:
return rec.foreground or ""
else:
return rec.colour
return ""
def remove_colour_substring(text: str) -> str:
"""
Remove text that identifies the colour to be used if strip_substring is True
"""
(rec, stripped_text) = _get_colour_record(text)
return stripped_text
# Track functions
@ -115,6 +137,34 @@ def create_track(path: str) -> TrackDTO:
return new_track
def update_track(path: str, track_id: int) -> TrackDTO:
"""
Update an existing track db entry return the DTO
"""
metadata = helpers.get_all_track_metadata(path)
with db.Session() as session:
track = session.get(Tracks, track_id)
if not track:
raise ApplicationError(f"Can't retrieve Track ({track_id=})")
track.path = (str(metadata["path"]),)
track.title = (str(metadata["title"]),)
track.artist = (str(metadata["artist"]),)
track.duration = (int(metadata["duration"]),)
track.start_gap = (int(metadata["start_gap"]),)
track.fade_at = (int(metadata["fade_at"]),)
track.silence_at = (int(metadata["silence_at"]),)
track.bitrate = (int(metadata["bitrate"]),)
session.commit()
updated_track = track_by_id(track_id)
if not updated_track:
raise ApplicationError("Unable to retrieve updated track")
return updated_track
def get_all_tracks() -> list[TrackDTO]:
"""Return a list of all tracks"""
@ -245,10 +295,7 @@ def track_with_path(path: str) -> bool:
with db.Session() as session:
track = (
session.execute(
select(Tracks)
.where(Tracks.path == path)
)
session.execute(select(Tracks).where(Tracks.path == path))
.scalars()
.one_or_none()
)
@ -272,6 +319,28 @@ def tracks_like_title(filter_str: str) -> list[TrackDTO]:
return _tracks_where(Tracks.title.ilike(f"%{filter_str}%"))
def get_last_played_dates(track_id: int, limit: int = 5) -> str:
"""
Return the most recent 'limit' dates that this track has been played
as a text list
"""
with db.Session() as session:
playdates = session.scalars(
Playdates.select()
.where(Playdates.track_id == track_id)
.order_by(Playdates.lastplayed.desc())
.limit(limit)
).all()
return "<br>".join(
[
a.lastplayed.strftime(Config.LAST_PLAYED_TOOLTIP_DATE_FORMAT)
for a in playdates
]
)
# Playlist functions
def _check_playlist_integrity(
session: Session, playlist_id: int, fix: bool = False
@ -305,6 +374,7 @@ def _check_playlist_integrity(
raise ApplicationError(msg)
@log_call
def _shift_rows(
session: Session, playlist_id: int, starting_row: int, shift_by: int
) -> None:
@ -313,8 +383,6 @@ def _shift_rows(
down; if -ve, shift them up.
"""
log.debug(f"(_shift_rows_down({playlist_id=}, {starting_row=}, {shift_by=}")
session.execute(
update(PlaylistRows)
.where(
@ -325,8 +393,12 @@ def _shift_rows(
)
@log_call
def move_rows(
from_rows: list[int], from_playlist_id: int, to_row: int, to_playlist_id: int | None = None
from_rows: list[int],
from_playlist_id: int,
to_row: int,
to_playlist_id: int | None = None,
) -> None:
"""
Move rows with or between playlists.
@ -341,10 +413,6 @@ def move_rows(
- 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:
@ -690,6 +758,25 @@ def insert_row(
return new_playlist_row
@log_call
def remove_comments(playlist_id: int, row_numbers: list[int]) -> None:
"""
Remove comments from rows in playlist
"""
with db.Session() as session:
session.execute(
update(PlaylistRows)
.where(
PlaylistRows.playlist_id == playlist_id,
PlaylistRows.row_number.in_(row_numbers),
)
.values(note="")
)
session.commit()
@log_call
def remove_rows(playlist_id: int, row_numbers: list[int]) -> None:
"""
Remove rows from playlist
@ -697,8 +784,6 @@ def remove_rows(playlist_id: int, row_numbers: list[int]) -> None:
Delete from highest row back so that not yet deleted row numbers don't change.
"""
log.debug(f"remove_rows({playlist_id=}, {row_numbers=}")
with db.Session() as session:
for row_number in sorted(row_numbers, reverse=True):
session.execute(
@ -749,9 +834,11 @@ def get_setting(name: str) -> int | None:
"""
with db.Session() as session:
record = session.execute(
select(Settings).where(Settings.name == name)
).scalars().one_or_none()
record = (
session.execute(select(Settings).where(Settings.name == name))
.scalars()
.one_or_none()
)
if not record:
return None
@ -764,9 +851,11 @@ def set_setting(name: str, value: int) -> None:
"""
with db.Session() as session:
record = session.execute(
select(Settings).where(Settings.name == name)
).scalars().one_or_none()
record = (
session.execute(select(Settings).where(Settings.name == name))
.scalars()
.one_or_none()
)
if not record:
record = Settings(session=session, name=name)
if not record: