Rebase dev onto v2_id

This commit is contained in:
Keith Edmunds 2022-03-02 09:27:10 +00:00
parent a91309477b
commit bc6a4c11cf
4 changed files with 356 additions and 299 deletions

View File

@ -51,8 +51,10 @@ engine = sqlalchemy.create_engine(
echo=Config.DISPLAY_SQL, echo=Config.DISPLAY_SQL,
pool_pre_ping=True) pool_pre_ping=True)
sm: sessionmaker = sessionmaker(bind=engine) # Create a Session factory
Session = scoped_session(sm) Session = scoped_session(sessionmaker(bind=engine))
# sm: sessionmaker = sessionmaker(bind=engine) # , expire_on_commit=False)
# Session = scoped_session(sm)
Base: DeclarativeMeta = declarative_base() Base: DeclarativeMeta = declarative_base()
Base.metadata.create_all(engine) Base.metadata.create_all(engine)
@ -105,7 +107,7 @@ class NoteColours(Base):
Optional["NoteColours"]: Optional["NoteColours"]:
"""Return record identified by id, or None if not found""" """Return record identified by id, or None if not found"""
return session.query(NoteColours).local_filter( return session.query(NoteColours).filter(
NoteColours.id == note_id).first() NoteColours.id == note_id).first()
@staticmethod @staticmethod
@ -116,7 +118,7 @@ class NoteColours(Base):
for rec in ( for rec in (
session.query(NoteColours) session.query(NoteColours)
.local_filter(NoteColours.enabled.is_(True)) .filter(NoteColours.enabled.is_(True))
.order_by(NoteColours.order) .order_by(NoteColours.order)
.all() .all()
): ):
@ -213,7 +215,7 @@ class Playdates(Base):
"""Return datetime track last played or None""" """Return datetime track last played or None"""
last_played: Optional[Playdates] = session.query( last_played: Optional[Playdates] = session.query(
Playdates.lastplayed).local_filter((Playdates.track_id == track_id) Playdates.lastplayed).filter((Playdates.track_id == track_id)
).order_by(Playdates.lastplayed.desc()).first() ).order_by(Playdates.lastplayed.desc()).first()
if last_played: if last_played:
return last_played[0] return last_played[0]
@ -226,7 +228,7 @@ class Playdates(Base):
Remove all records of track_id Remove all records of track_id
""" """
session.query(Playdates).local_filter( session.query(Playdates).filter(
Playdates.track_id == track_id, Playdates.track_id == track_id,
).delete() ).delete()
session.commit() session.commit()
@ -293,7 +295,7 @@ class Playlists(Base):
@classmethod @classmethod
def get_by_id(cls, session: Session, playlist_id: int) -> "Playlists": def get_by_id(cls, session: Session, playlist_id: int) -> "Playlists":
return (session.query(cls).local_filter(cls.id == playlist_id)).one() return (session.query(cls).filter(cls.id == playlist_id)).one()
@classmethod @classmethod
def get_closed(cls, session: Session) -> List["Playlists"]: def get_closed(cls, session: Session) -> List["Playlists"]:
@ -301,7 +303,7 @@ class Playlists(Base):
return ( return (
session.query(cls) session.query(cls)
.local_filter(cls.loaded.is_(False)) .filter(cls.loaded.is_(False))
.order_by(cls.last_used.desc()) .order_by(cls.last_used.desc())
).all() ).all()
@ -313,7 +315,7 @@ class Playlists(Base):
return ( return (
session.query(cls) session.query(cls)
.local_filter(cls.loaded.is_(True)) .filter(cls.loaded.is_(True))
.order_by(cls.last_used.desc()) .order_by(cls.last_used.desc())
).all() ).all()
@ -330,7 +332,7 @@ class Playlists(Base):
Remove all tracks from this playlist Remove all tracks from this playlist
""" """
session.query(PlaylistTracks).local_filter( session.query(PlaylistTracks).filter(
PlaylistTracks.playlist_id == self.id, PlaylistTracks.playlist_id == self.id,
).delete() ).delete()
session.commit() session.commit()
@ -338,7 +340,7 @@ class Playlists(Base):
def remove_track(self, session: Session, row: int) -> None: def remove_track(self, session: Session, row: int) -> None:
DEBUG(f"Playlist.remove_track({self.id=}, {row=})") DEBUG(f"Playlist.remove_track({self.id=}, {row=})")
session.query(PlaylistTracks).local_filter( session.query(PlaylistTracks).filter(
PlaylistTracks.playlist_id == self.id, PlaylistTracks.playlist_id == self.id,
PlaylistTracks.row == row PlaylistTracks.row == row
).delete() ).delete()
@ -389,7 +391,7 @@ class PlaylistTracks(Base):
new_row: int new_row: int
max_row: Optional[int] = session.query( max_row: Optional[int] = session.query(
func.max(PlaylistTracks.row)).local_filter( func.max(PlaylistTracks.row)).filter(
PlaylistTracks.playlist_id == to_playlist_id).scalar() PlaylistTracks.playlist_id == to_playlist_id).scalar()
if max_row is None: if max_row is None:
# Destination playlist is empty; use row 0 # Destination playlist is empty; use row 0
@ -398,7 +400,7 @@ class PlaylistTracks(Base):
# Destination playlist has tracks; add to end # Destination playlist has tracks; add to end
new_row = max_row + 1 new_row = max_row + 1
try: try:
record: PlaylistTracks = session.query(PlaylistTracks).local_filter( record: PlaylistTracks = session.query(PlaylistTracks).filter(
PlaylistTracks.playlist_id == from_playlist_id, PlaylistTracks.playlist_id == from_playlist_id,
PlaylistTracks.row == row).one() PlaylistTracks.row == row).one()
except NoResultFound: except NoResultFound:
@ -446,7 +448,7 @@ class Settings(Base):
int_setting: Settings int_setting: Settings
try: try:
int_setting = session.query(cls).local_filter( int_setting = session.query(cls).filter(
cls.name == name).one() cls.name == name).one()
except NoResultFound: except NoResultFound:
int_setting = Settings() int_setting = Settings()
@ -516,7 +518,7 @@ class Tracks(Base):
DEBUG(f"Tracks.get_or_create({path=})") DEBUG(f"Tracks.get_or_create({path=})")
try: try:
track = session.query(cls).local_filter(cls.path == path).one() track = session.query(cls).filter(cls.path == path).one()
except NoResultFound: except NoResultFound:
track = Tracks(session, path) track = Tracks(session, path)
@ -533,7 +535,7 @@ class Tracks(Base):
DEBUG(f"Tracks.get_track_from_filename({filename=})") DEBUG(f"Tracks.get_track_from_filename({filename=})")
try: try:
track = session.query(Tracks).local_filter(Tracks.path.ilike( track = session.query(Tracks).filter(Tracks.path.ilike(
f'%{os.path.sep}{filename}')).one() f'%{os.path.sep}{filename}')).one()
return track return track
except (NoResultFound, MultipleResultsFound): except (NoResultFound, MultipleResultsFound):
@ -547,7 +549,7 @@ class Tracks(Base):
DEBUG(f"Tracks.get_track_from_path({path=})") DEBUG(f"Tracks.get_track_from_path({path=})")
return session.query(Tracks).local_filter(Tracks.path == path).first() return session.query(Tracks).filter(Tracks.path == path).first()
@classmethod @classmethod
def get_by_id(cls, session: Session, track_id: int) -> Optional["Tracks"]: def get_by_id(cls, session: Session, track_id: int) -> Optional["Tracks"]:
@ -555,7 +557,7 @@ class Tracks(Base):
try: try:
DEBUG(f"Tracks.get_track(track_id={track_id})") DEBUG(f"Tracks.get_track(track_id={track_id})")
track = session.query(Tracks).local_filter(Tracks.id == track_id).one() track = session.query(Tracks).filter(Tracks.id == track_id).one()
return track return track
except NoResultFound: except NoResultFound:
ERROR(f"get_track({track_id}): not found") ERROR(f"get_track({track_id}): not found")
@ -584,7 +586,7 @@ class Tracks(Base):
DEBUG(f"Tracks.remove_path({path=})") DEBUG(f"Tracks.remove_path({path=})")
try: try:
session.query(Tracks).local_filter(Tracks.path == path).delete() session.query(Tracks).filter(Tracks.path == path).delete()
session.commit() session.commit()
except IntegrityError as exception: except IntegrityError as exception:
ERROR(f"Can't remove track with {path=} ({exception=})") ERROR(f"Can't remove track with {path=} ({exception=})")
@ -594,7 +596,7 @@ class Tracks(Base):
return ( return (
session.query(cls) session.query(cls)
.local_filter(cls.artist.ilike(f"%{text}%")) .filter(cls.artist.ilike(f"%{text}%"))
.order_by(cls.title) .order_by(cls.title)
).all() ).all()
@ -602,7 +604,7 @@ class Tracks(Base):
def search_titles(cls, session: Session, text: str) -> List["Tracks"]: def search_titles(cls, session: Session, text: str) -> List["Tracks"]:
return ( return (
session.query(cls) session.query(cls)
.local_filter(cls.title.ilike(f"%{text}%")) .filter(cls.title.ilike(f"%{text}%"))
.order_by(cls.title) .order_by(cls.title)
).all() ).all()

View File

@ -86,6 +86,8 @@ class Music:
p.stop() p.stop()
DEBUG(f"Releasing player {p=}", True) DEBUG(f"Releasing player {p=}", True)
p.release() p.release()
# Ensure we don't reference player after release
p = None
self.fading -= 1 self.fading -= 1

View File

@ -412,7 +412,8 @@ class Window(QMainWindow, Ui_MainWindow):
rows = [] rows = []
for (row, track) in ( for (row, track) in (
self.visible_playlist_tab().get_selected_rows_and_tracks() self.visible_playlist_tab().get_selected_rows_and_tracks(
session)
): ):
rows.append(row) rows.append(row)
if destination_visible_playlist_tab: if destination_visible_playlist_tab:
@ -436,8 +437,14 @@ class Window(QMainWindow, Ui_MainWindow):
Ensure we have info tabs for next and current track titles Ensure we have info tabs for next and current track titles
""" """
title_list: List[str, str] = [self.previous_track.title, title_list: List[str] = []
self.current_track.title]
if self.previous_track:
title_list.append(self.previous_track.title)
if self.current_track:
title_list.append(self.current_track.title)
if self.next_track:
title_list.append(self.next_track.title)
for title in title_list: for title in title_list:
if title in self.info_tabs.keys(): if title in self.info_tabs.keys():
@ -533,7 +540,7 @@ class Window(QMainWindow, Ui_MainWindow):
Playdates(session, self.current_track) Playdates(session, self.current_track)
# Tell playlist track is now playing # Tell playlist track is now playing
self.current_track_playlist_tab.play_started() self.current_track_playlist_tab.play_started(session)
# Disable play next controls # Disable play next controls
self.disable_play_next_controls() self.disable_play_next_controls()
@ -680,11 +687,12 @@ class Window(QMainWindow, Ui_MainWindow):
""" """
with Session() as session:
# Clear next track if on another tab # Clear next track if on another tab
if self.next_track_playlist_tab != playlist_tab: if self.next_track_playlist_tab != playlist_tab:
# We need to reset the ex-next-track playlist # We need to reset the ex-next-track playlist
if self.next_track_playlist_tab: if self.next_track_playlist_tab:
self.next_track_playlist_tab.clear_next() self.next_track_playlist_tab.clear_next(session)
# Reset tab colour if on other tab # Reset tab colour if on other tab
if (self.next_track_playlist_tab != if (self.next_track_playlist_tab !=
@ -739,7 +747,10 @@ class Window(QMainWindow, Ui_MainWindow):
# If track is playing, update track clocks time and colours # If track is playing, update track clocks time and colours
if self.music.player and self.music.playing(): if self.music.player and self.music.playing():
with Session() as session:
self.playing = True self.playing = True
if self.current_track not in session:
session.add(self.current_track)
playtime: int = self.music.get_playtime() playtime: int = self.music.get_playtime()
time_to_fade: int = (self.current_track.fade_at - playtime) time_to_fade: int = (self.current_track.fade_at - playtime)
time_to_silence: int = (self.current_track.silence_at - playtime) time_to_silence: int = (self.current_track.silence_at - playtime)

View File

@ -16,6 +16,7 @@ from PyQt5.QtWidgets import (
QTableWidgetItem, QTableWidgetItem,
) )
from sqlalchemy import inspect from sqlalchemy import inspect
from sqlalchemy.orm.exc import DetachedInstanceError
import helpers import helpers
import os import os
@ -192,9 +193,9 @@ class PlaylistTab(QTableWidget):
f"Moved row(s) {rows} to become row {drop_row}" f"Moved row(s) {rows} to become row {drop_row}"
) )
with Session() as session: with Session() as session: # checked
self.save_playlist(session) self.save_playlist(session)
self.update_display() self.update_display(session)
def edit(self, index, trigger, event): # review def edit(self, index, trigger, event): # review
result = super(PlaylistTab, self).edit(index, trigger, event) result = super(PlaylistTab, self).edit(index, trigger, event)
@ -249,7 +250,7 @@ class PlaylistTab(QTableWidget):
def closeEvent(self, event) -> None: def closeEvent(self, event) -> None:
"""Save column widths""" """Save column widths"""
with Session() as session: with Session() as session: # checked
for column in range(self.columnCount()): for column in range(self.columnCount()):
width = self.columnWidth(column) width = self.columnWidth(column)
name = f"playlist_col_{str(column)}_width" name = f"playlist_col_{str(column)}_width"
@ -259,11 +260,11 @@ class PlaylistTab(QTableWidget):
event.accept() event.accept()
def clear_next(self) -> None: def clear_next(self, session) -> None:
"""Clear next track""" """Clear next track"""
self._meta_clear_next() self._meta_clear_next()
self.update_display() self.update_display(session)
def create_note(self) -> None: def create_note(self) -> None:
""" """
@ -287,7 +288,7 @@ class PlaylistTab(QTableWidget):
with Session() as session: with Session() as session:
note: Notes = Notes( note: Notes = Notes(
session, self.playlist.id, row, dlg.textValue()) session, self.playlist.id, row, dlg.textValue())
self._insert_note(session, note, row, True) self._insert_note(session, note, row, True) # checked
def get_selected_row(self) -> Optional[int]: def get_selected_row(self) -> Optional[int]:
"""Return row number of first selected row, or None if none selected""" """Return row number of first selected row, or None if none selected"""
@ -297,7 +298,7 @@ class PlaylistTab(QTableWidget):
else: else:
return self.selectionModel().selectedRows()[0].row() return self.selectionModel().selectedRows()[0].row()
def get_selected_rows_and_tracks(self) \ def get_selected_rows_and_tracks(self, session: Session) \
-> Optional[List[Tuple[int, Tracks]]]: -> Optional[List[Tuple[int, Tracks]]]:
"""Return a list of selected (row-number, track) tuples""" """Return a list of selected (row-number, track) tuples"""
@ -307,7 +308,7 @@ class PlaylistTab(QTableWidget):
result = [] result = []
for row in [r.row() for r in self.selectionModel().selectedRows()]: for row in [r.row() for r in self.selectionModel().selectedRows()]:
result.append((row, self._get_row_object(row))) result.append((row, self._get_row_object(row, session)))
return result return result
@ -378,14 +379,14 @@ class PlaylistTab(QTableWidget):
self._set_row_content(row, track) self._set_row_content(row, track)
# Mark track if file is unreadable # Mark track if file is unreadable
if not self._track_is_readable(track): if not self._file_is_readable(track.path):
self._meta_set_unreadable(row) self._meta_set_unreadable(row)
# Scroll to new row # Scroll to new row
self.scrollToItem(title_item, QAbstractItemView.PositionAtCenter) self.scrollToItem(title_item, QAbstractItemView.PositionAtCenter)
if repaint: if repaint:
self.save_playlist(session) self.save_playlist(session)
self.update_display(clear_selection=False) self.update_display(session, clear_selection=False)
def remove_rows(self, rows) -> None: def remove_rows(self, rows) -> None:
"""Remove rows passed in rows list""" """Remove rows passed in rows list"""
@ -398,10 +399,9 @@ class PlaylistTab(QTableWidget):
with Session() as session: with Session() as session:
self.save_playlist(session) self.save_playlist(session)
self.update_display(session)
self.update_display() def play_started(self, session: Session) -> None:
def play_started(self) -> None:
""" """
Notification from musicmuster that track has started playing. Notification from musicmuster that track has started playing.
@ -425,16 +425,17 @@ class PlaylistTab(QTableWidget):
self._meta_set_played(current_row) self._meta_set_played(current_row)
# Scroll to put current track in middle # Scroll to put current track in middle
scroll_to = self.item(current_row, self.COL_INDEX) scroll_to = self.item(current_row, self.COL_MSS)
self.scrollToItem(scroll_to, QAbstractItemView.PositionAtCenter) self.scrollToItem(scroll_to, QAbstractItemView.PositionAtCenter)
# Set next track # Set next track
search_from = current_row + 1 search_from = current_row + 1
next_row = self._find_next_track_row(search_from) next_row = self._find_next_track_row(search_from)
if next_row:
self._set_next(next_row) self._set_next(next_row)
# Update display # Update display
self.update_display() self.update_display(session)
def play_stopped(self) -> None: def play_stopped(self) -> None:
""" """
@ -448,7 +449,6 @@ class PlaylistTab(QTableWidget):
self._meta_clear_current() self._meta_clear_current()
self.current_track_start_time = None self.current_track_start_time = None
self.update_display()
def save_playlist(self, session) -> None: def save_playlist(self, session) -> None:
""" """
@ -474,7 +474,7 @@ class PlaylistTab(QTableWidget):
# PlaylistTab # PlaylistTab
for row in notes_rows: for row in notes_rows:
note: Notes = self._get_row_object(row) note: Notes = self._get_row_object(row, session)
session.add(note) session.add(note)
playlist_notes[note.id] = note playlist_notes[note.id] = note
@ -604,9 +604,9 @@ class PlaylistTab(QTableWidget):
if row is None: if row is None:
return None return None
self.set_next_track(row) self._set_next(row)
def update_display(self, clear_selection: bool = True) -> None: def update_display(self, session, clear_selection: bool = True) -> None:
""" """
Set row colours, fonts, etc Set row colours, fonts, etc
@ -618,6 +618,8 @@ class PlaylistTab(QTableWidget):
- Show unplayed tracks in bold - Show unplayed tracks in bold
""" """
if self.playlist not in session:
session.add(self.playlist)
DEBUG(f"playlist. update_display [{self.playlist=}]") DEBUG(f"playlist. update_display [{self.playlist=}]")
# Clear selection if required # Clear selection if required
@ -654,7 +656,6 @@ class PlaylistTab(QTableWidget):
if not start_times_row: if not start_times_row:
start_times_row = 0 start_times_row = 0
with Session() as session:
# Cycle through all rows # Cycle through all rows
for row in range(self.rowCount()): for row in range(self.rowCount()):
@ -696,7 +697,7 @@ class PlaylistTab(QTableWidget):
last_played_str) last_played_str)
# Calculate next_start_time # Calculate next_start_time
track = self._get_row_object(row) track = self._get_row_object(row, session)
next_start_time = self._calculate_track_end_time( next_start_time = self._calculate_track_end_time(
track, self.current_track_start_time) track, self.current_track_start_time)
@ -724,7 +725,7 @@ class PlaylistTab(QTableWidget):
self._set_row_start_time(row, start_time) self._set_row_start_time(row, start_time)
# Set end time # Set end time
track = self._get_row_object(row) track = self._get_row_object(row, session)
next_start_time = self._calculate_track_end_time( next_start_time = self._calculate_track_end_time(
track, start_time) track, start_time)
self._set_row_end_time(row, next_start_time) self._set_row_end_time(row, next_start_time)
@ -738,7 +739,7 @@ class PlaylistTab(QTableWidget):
else: else:
# This is a track row other than next or current # This is a track row other than next or current
track = self._get_row_object(row) track = self._get_row_object(row, session)
if row in played: if row in played:
# Played today, so update last played column # Played today, so update last played column
last_playedtime = Playdates.last_played( last_playedtime = Playdates.last_played(
@ -779,7 +780,8 @@ class PlaylistTab(QTableWidget):
if row in self._meta_get_notes(): if row in self._meta_get_notes():
return None return None
track: Tracks = self._get_row_object(row) with Session() as session:
track: Tracks = self._get_row_object(row, session)
open_in_audacity(track.path) open_in_audacity(track.path)
@staticmethod @staticmethod
@ -811,7 +813,8 @@ class PlaylistTab(QTableWidget):
if row in self._meta_get_notes(): if row in self._meta_get_notes():
return None return None
track: Optional[Tracks] = self._get_row_object(row) with Session() as session:
track: Optional[Tracks] = self._get_row_object(row, session)
if track: if track:
cb: QApplication.clipboard = QApplication.clipboard() cb: QApplication.clipboard = QApplication.clipboard()
cb.clear(mode=cb.Clipboard) cb.clear(mode=cb.Clipboard)
@ -828,8 +831,9 @@ class PlaylistTab(QTableWidget):
new_text: str = self.item(row, column).text() new_text: str = self.item(row, column).text()
DEBUG(f"_cell_changed({row=}, {column=}, {new_text=}") DEBUG(f"_cell_changed({row=}, {column=}, {new_text=}")
row_object: Union[Tracks, Notes] = self._get_row_object(row)
with Session() as session: with Session() as session:
row_object: Union[Tracks, Notes] = self._get_row_object(
row, session)
if row in self._meta_get_notes(): if row in self._meta_get_notes():
# Save change to database # Save change to database
DEBUG(f"Notes.update_note: saving new note text '{new_text=}'") DEBUG(f"Notes.update_note: saving new note text '{new_text=}'")
@ -866,7 +870,8 @@ class PlaylistTab(QTableWidget):
# update_display to update start times, such as when a note has # update_display to update start times, such as when a note has
# been edited # been edited
self.update_display() with Session() as session:
self.update_display(session)
self.parent.enable_play_next_controls() self.parent.enable_play_next_controls()
@ -911,7 +916,7 @@ class PlaylistTab(QTableWidget):
# delete in reverse row order so row numbers don't # delete in reverse row order so row numbers don't
# change # change
for row in sorted(rows_to_delete, reverse=True): for row in sorted(rows_to_delete, reverse=True):
row_object = self._get_row_object(row) row_object = self._get_row_object(row, session)
if row in note_rows: if row in note_rows:
row_object.delete_note(session) row_object.delete_note(session)
else: else:
@ -919,7 +924,7 @@ class PlaylistTab(QTableWidget):
self.removeRow(row) self.removeRow(row)
self.save_playlist(session) self.save_playlist(session)
self.update_display() self.update_display(session)
def _drop_on(self, event): # review def _drop_on(self, event): # review
index = self.indexAt(event.pos()) index = self.indexAt(event.pos())
@ -958,10 +963,14 @@ class PlaylistTab(QTableWidget):
except ValueError: except ValueError:
return None return None
def _get_row_object(self, row: int) -> Union[Tracks, Notes]: def _get_row_object(self, row: int, session: Session) \
-> Union[Tracks, Notes]:
"""Return content associated with this row""" """Return content associated with this row"""
return self.item(row, self.COL_USERDATA).data(self.CONTENT_OBJECT) obj = self.item(row, self.COL_USERDATA).data(self.CONTENT_OBJECT)
if obj not in session:
session.add(obj)
return obj
def _get_row_start_time(self, row: int) -> Optional[datetime]: def _get_row_start_time(self, row: int) -> Optional[datetime]:
try: try:
@ -980,7 +989,9 @@ class PlaylistTab(QTableWidget):
txt: str txt: str
row_object: Union[Tracks, Notes] = self._get_row_object(row) with Session() as session:
row_object: Union[Tracks, Notes] = self._get_row_object(
row, session)
if row in self._meta_get_notes(): if row in self._meta_get_notes():
txt = row_object.note txt = row_object.note
else: else:
@ -1040,7 +1051,7 @@ class PlaylistTab(QTableWidget):
if repaint: if repaint:
self.save_playlist(session) self.save_playlist(session)
self.update_display(clear_selection=False) self.update_display(session, clear_selection=False)
def _is_below(self, pos, index): # review def _is_below(self, pos, index): # review
rect = self.visualRect(index) rect = self.visualRect(index)
@ -1110,6 +1121,13 @@ class PlaylistTab(QTableWidget):
current_row: Optional[int] = self._meta_get_current() current_row: Optional[int] = self._meta_get_current()
if current_row is not None: if current_row is not None:
self._meta_clear_attribute(current_row, RowMeta.CURRENT) self._meta_clear_attribute(current_row, RowMeta.CURRENT)
# Reset row colour
if current_row % 2:
self._set_row_colour(
current_row, QColor(Config.COLOUR_ODD_PLAYLIST))
else:
self._set_row_colour(
current_row, QColor(Config.COLOUR_EVEN_PLAYLIST))
def _meta_clear_next(self) -> None: def _meta_clear_next(self) -> None:
""" """
@ -1274,7 +1292,7 @@ class PlaylistTab(QTableWidget):
# We possibly don't need to save the playlist here, but row # We possibly don't need to save the playlist here, but row
# numbers may have changed during population, and it's cheap to do # numbers may have changed during population, and it's cheap to do
self.save_playlist(session) self.save_playlist(session)
self.update_display() self.update_display(session)
def _rescan(self, row: int) -> None: def _rescan(self, row: int) -> None:
""" """
@ -1287,11 +1305,11 @@ class PlaylistTab(QTableWidget):
if row in self._meta_get_notes(): if row in self._meta_get_notes():
return None return None
track: Tracks = self._get_row_object(row)
if track:
with Session() as session: with Session() as session:
track: Tracks = self._get_row_object(row, session)
if track:
track.rescan(session) track.rescan(session)
self._update_row(row, track) self._update_row(session, row, track)
def _select_event(self) -> None: def _select_event(self) -> None:
""" """
@ -1304,7 +1322,11 @@ class PlaylistTab(QTableWidget):
track_rows = list(row_set - note_row_set) track_rows = list(row_set - note_row_set)
tracks: List[Tracks] tracks: List[Tracks]
tracks = [self._get_row_object(row) for row in track_rows] with Session() as session: # checked
tracks = [self._get_row_object(row, session) for row in track_rows]
for track in tracks:
if track not in session:
session.add(track)
ms: int = sum([track.duration for track in tracks]) ms: int = sum([track.duration for track in tracks])
# Only paint message if there are selected track rows # Only paint message if there are selected track rows
@ -1340,15 +1362,16 @@ class PlaylistTab(QTableWidget):
DEBUG(f"_set_next({row=})") DEBUG(f"_set_next({row=})")
with Session() as session:
# Check row is a track row # Check row is a track row
if row in self._meta_get_notes(): if row in self._meta_get_notes():
return None return None
track: Tracks = self._get_row_object(row) track: Tracks = self._get_row_object(row, session)
if not track: if not track:
return None return None
# Check track is readable # Check track is readable
if not self._track_is_readable(track): if not self._file_is_readable(track.path):
self._meta_set_unreadable(row) self._meta_set_unreadable(row)
return None return None
@ -1359,7 +1382,7 @@ class PlaylistTab(QTableWidget):
self.parent.this_is_the_next_track(self, track) self.parent.this_is_the_next_track(self, track)
# Update display # Update display
self.update_display() self.update_display(session)
def _set_row_bold(self, row: int, bold: bool = True) -> None: def _set_row_bold(self, row: int, bold: bool = True) -> None:
"""Make row bold (bold=True) or not bold""" """Make row bold (bold=True) or not bold"""
@ -1415,23 +1438,42 @@ class PlaylistTab(QTableWidget):
item: QTableWidgetItem = QTableWidgetItem(time_str) item: QTableWidgetItem = QTableWidgetItem(time_str)
self.setItem(row, self.COL_START_TIME, item) self.setItem(row, self.COL_START_TIME, item)
def _track_path_is_readable(self, track_id): def _select_tracks(self, played: bool) -> None:
"""
Select all played (played=True) or unplayed (played=False)
tracks in playlist
"""
# Need to allow multiple rows to be selected
self.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
notes_rows: List[int] = self._meta_get_notes()
self.clearSelection()
played_rows: List[int] = self._meta_get_played()
for row in range(self.rowCount()):
if row in notes_rows:
continue
if row in played_rows == played:
self.selectRow(row)
# Reset extended selection
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
@staticmethod
def _file_is_readable(path: str) -> bool:
""" """
Returns True if track path is readable, else False Returns True if track path is readable, else False
vlc cannot read files with a colon in the path vlc cannot read files with a colon in the path
""" """
with Session() as session:
path = Tracks.get_path(session, track_id)
if os.access(path, os.R_OK): if os.access(path, os.R_OK):
if ':' not in path: if ':' not in path:
return True return True
return False return False
def _update_row(self, row: int, track: Tracks) -> None: def _update_row(self, session, row: int, track: Tracks) -> None:
""" """
Update the passed row with info from the passed track. Update the passed row with info from the passed track.
""" """
@ -1454,4 +1496,4 @@ class PlaylistTab(QTableWidget):
item_duration: QTableWidgetItem = self.item(row, self.COL_DURATION) item_duration: QTableWidgetItem = self.item(row, self.COL_DURATION)
item_duration.setText(helpers.ms_to_mmss(track.duration)) item_duration.setText(helpers.ms_to_mmss(track.duration))
self.update_display() self.update_display(session)