Rewrite of save_playlist (extensive)

This commit is contained in:
Keith Edmunds 2021-04-25 17:23:36 +01:00
parent d113b9fc20
commit 839f550e4a
2 changed files with 166 additions and 105 deletions

View File

@ -13,7 +13,7 @@ from sqlalchemy import (
String,
func
)
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
from sqlalchemy.orm import relationship, sessionmaker
from config import Config
@ -63,7 +63,7 @@ class Notes(Base):
@staticmethod
def delete_note(id):
DEBUG(f"delete_note(id={id}")
DEBUG(f"delete_note(id={id}")
session.query(Notes).filter(Notes.id == id).delete()
session.commit()
@ -222,26 +222,51 @@ class PlaylistTracks(Base):
return last_row + 1
@staticmethod
def remove_track(playlist_id, track_id):
DEBUG(f"remove_track(playlist_id={playlist_id}, track_id={track_id})")
session.query(PlaylistTracks).filter(
PlaylistTracks.playlist_id == playlist_id,
PlaylistTracks.track_id == track_id
).delete()
def add_track(playlist_id, track_id, row):
DEBUG(
f"PlaylistTracks.add_track(playlist_id={playlist_id}, "
f"track_id={track_id}, row={row})"
)
plt = PlaylistTracks()
plt.playlist_id = playlist_id,
plt.track_id = track_id,
plt.row = row
session.add(plt)
session.commit()
@staticmethod
def update_track_row(playlist_id, track_id, old_row, new_row):
def remove_track(playlist_id, row):
DEBUG(
f"update_track_row(playlist_id={playlist_id}, "
f"track_id={track_id}, old_row={old_row}, new_row={new_row})"
f"PlaylistTracks.remove_track(playlist_id={playlist_id}, "
f"row={row})"
)
session.query(PlaylistTracks).filter(
PlaylistTracks.playlist_id == playlist_id,
PlaylistTracks.row == row
).delete()
session.commit()
@staticmethod
def update_row_track(playlist_id, row, track_id):
DEBUG(
f"PlaylistTracks.update_track_row(playlist_id={playlist_id}, "
f"row={row}, track_id={track_id})"
)
plt = session.query(PlaylistTracks).filter(
PlaylistTracks.playlist_id == playlist_id,
PlaylistTracks.track_id == track_id,
PlaylistTracks.row == old_row
).one()
plt.row = new_row
try:
plt = session.query(PlaylistTracks).filter(
PlaylistTracks.playlist_id == playlist_id,
PlaylistTracks.row == row
).one()
except MultipleResultsFound:
ERROR(
f"Multiple rows matched in query: "
f"PlaylistTracks.playlist_id == {playlist_id}, "
f"PlaylistTracks.row == {row}"
)
return
plt.track_id = track_id
session.commit()

View File

@ -125,6 +125,7 @@ class Playlist(QTableWidget):
f"Moved row(s) {rows} to become row {drop_row}"
)
self._save_playlist()
self._repaint()
def eventFilter(self, source, event):
@ -172,12 +173,13 @@ class Playlist(QTableWidget):
Notes object.
"""
DEBUG(f"add_to_playlist(data={data}, repaint={repaint}, row={row}")
if not row:
if self.selectionModel().hasSelection():
row = self.currentRow()
else:
row = self.rowCount()
DEBUG(f"add_to_playlist(data={data}): row={row}")
DEBUG(f"add_to_playlist: row set to {row}")
self.insertRow(row)
@ -196,8 +198,7 @@ class Playlist(QTableWidget):
self.setItem(row, self.COL_DURATION, item)
item = QTableWidgetItem(track.path)
self.setItem(row, self.COL_PATH, item)
else:
# This is a note
elif isinstance(data, Notes):
DEBUG(f"add_to_playlist: note.id={data.id}")
note = data
@ -220,7 +221,7 @@ class Playlist(QTableWidget):
self.setSpan(row, self.COL_NOTE, self.NOTE_ROW_SPAN,
self.NOTE_COL_SPAN)
# Add start times or empty items, otherwise background
# Add start times or empty items as background
# colour won't be set for columns without items
self._set_row_time(row, start_time)
item = QTableWidgetItem()
@ -228,10 +229,15 @@ class Playlist(QTableWidget):
self._meta_set_note(row)
else:
ERROR(f"Unknown data passed to add_to_playlist({data})")
return
# Scroll to new row
self.scrollToItem(titleitem, QAbstractItemView.PositionAtCenter)
if repaint:
self._save_playlist()
self._repaint(clear_selection=False)
def create_playlist(self, name):
@ -241,6 +247,11 @@ class Playlist(QTableWidget):
self.load_playlist(new_id)
def fade(self):
"Fade currently playing track"
if not self.current_track:
return
self.previous_track = self.current_track
self.previous_track_position = self.music.fade()
@ -302,7 +313,9 @@ class Playlist(QTableWidget):
"""
Load tracks and notes from playlist id.
Set first track as next track to play.
If this is not the first playlist loaded, we may already have
a next track set in which case don't change it. Otherwise, set
the first non-notes row as next track to play.
"""
DEBUG(f"load_playlist(plid={plid})")
@ -330,9 +343,7 @@ class Playlist(QTableWidget):
for item in sorted(data, key=lambda x: x[0]):
self.add_to_playlist(item[1], repaint=False)
# If this is not the first playlist loaded, we may already have
# a next track set in which case don't change it. Otherwise, set
# the first non-notes row as next track to play.
# Set next track if we don't have one already set
if not self.next_track:
notes_rows = self._meta_get_notes()
for row in range(self.rowCount()):
@ -340,6 +351,8 @@ class Playlist(QTableWidget):
continue
self._cue_next_track(row)
break
else:
self._repaint()
# Scroll to top
scroll_to = self.item(0, self.COL_INDEX)
@ -405,9 +418,8 @@ class Playlist(QTableWidget):
for row in range(start, self.rowCount()):
if row in notes_rows:
continue
if self.item(row, self.COL_INDEX):
self._cue_next_track(row)
break
self._cue_next_track(row)
break
# Tell database to record it as played
self.current_track.update_lastplayed()
@ -468,8 +480,7 @@ class Playlist(QTableWidget):
DEBUG("_calculate_next_start_time() called with row=None")
return None
duration = Tracks.get_duration(
int(self.item(row, self.COL_INDEX).text()))
duration = Tracks.get_duration(self._get_row_id(row))
return start + timedelta(milliseconds=duration)
def _can_read_track(self, track):
@ -481,36 +492,40 @@ class Playlist(QTableWidget):
self.menu.exec_(self.mapToGlobal(pos))
def _cue_next_track(self, next_row):
def _cue_next_track(self, row):
"""
Set the passed row as the next track to play
"""
if next_row is not None:
self._meta_set_next(next_row)
track_id = int(self.item(next_row, self.COL_INDEX).text())
if not self.next_track or self.next_track.id != track_id:
self.next_track = Tracks.get_track(track_id)
# Check we can read it
if not self._can_read_track(self.next_track):
self.parent().parent().show_warning(
"Can't read next track",
self.next_track.path)
else:
self.next_track = None
track_id = self._get_row_id(row)
if not track_id:
return
self._meta_set_next(row)
if not self.next_track or self.next_track.id != track_id:
self.next_track = Tracks.get_track(track_id)
# Check we can read it
if not self._can_read_track(self.next_track):
self.parent().parent().show_warning(
"Can't read next track",
self.next_track.path)
# Update display
self._repaint()
def _delete_row(self, row):
"Delete row"
DEBUG(f"playlist._delete_row({row})")
if row == self._meta_get_current():
# TODO
DEBUG("playlist._delete_row(): Can't delete playing track")
return
elif row == self._meta_get_next():
# TODO
DEBUG("playlist._delete_row(): Can't delete next track")
return
else:
title = self.item(row, self.COL_TITLE).text()
@ -522,12 +537,11 @@ class Playlist(QTableWidget):
msg.setDefaultButton(QMessageBox.Cancel)
msg.setWindowTitle("Delete row")
if msg.exec() == QMessageBox.Yes:
DEBUG(f"playlist._delete_row(): Delete row {row}")
id = int(self.item(row, self.COL_INDEX).text())
id = self._get_row_id(row)
if row in self._meta_get_notes():
Notes.delete_note(id)
else:
PlaylistTracks.remove_track(self.playlist_id, id)
PlaylistTracks.remove_track(self.playlist_id, row)
self.removeRow(row)
self._repaint()
@ -540,6 +554,21 @@ class Playlist(QTableWidget):
return (index.row() + 1 if self._is_below(event.pos(), index)
else index.row())
def _get_row_id(self, row):
"Return item id as integer from passed row"
if self.item(row, self.COL_INDEX):
try:
return int(self.item(row, self.COL_INDEX).text())
except TypeError:
ERROR(
f"_get_row_id({row}): error retrieving row id "
f"({self.item(row, self.COL_INDEX).text()})"
)
else:
ERROR(f"(_get_row_id({row}): no COL_INDEX data in row")
return None
def _get_row_time(self, row):
try:
if self.item(row, self.COL_START_TIME):
@ -669,6 +698,8 @@ class Playlist(QTableWidget):
be played and return True. Otherwise return False.
"""
DEBUG(f"_set_next({row})")
if row in self._meta_get_notes():
return False
@ -683,8 +714,6 @@ class Playlist(QTableWidget):
DEBUG(f"_repaint(clear_selection={clear_selection})")
self._save_playlist()
if clear_selection:
self.clearSelection()
current = self._meta_get_current()
@ -750,8 +779,7 @@ class Playlist(QTableWidget):
colour = QColor(Config.COLOUR_EVEN_PLAYLIST)
self._set_row_colour(row, colour)
if (int(self.item(row, self.COL_INDEX).text()) in
self.played_tracks):
if self._get_row_id(row) in self.played_tracks:
self._set_row_not_bold(row)
else:
# Set time only if we haven't played it yet
@ -767,58 +795,36 @@ class Playlist(QTableWidget):
def _save_playlist(self):
"""
Save playlist to database. Add missing notes/tracks; remove any that
are in database but not playlist. Correct row number in database if
necessary.
Save playlist to database. We do this by correcting differences
between the on-screen (definitive) playlist and that in the
database.
We treat the notes rows and the tracks rows differently. Notes must
appear only once in only one playlist. Tracks can appear multiple
times in one playlist and in multiple playlists.
"""
note_rows = self._meta_get_notes()
playlist = Playlists.get_playlist_by_id(self.playlist_id)
# Create dictionaries indexed by track/note id
# Notes first
# Create dictionaries indexed by note_id
playlist_notes = {}
playlist_tracks = {}
database_notes = {}
database_tracks = {}
notes_rows = self._meta_get_notes()
# Playlist
for row in range(self.rowCount()):
# Get id of item
if self.item(row, self.COL_INDEX):
id = int(self.item(row, self.COL_INDEX).text())
else:
for row in notes_rows:
note_id = self._get_row_id(row)
if not note_id:
DEBUG(f"(_save_playlist(): no COL_INDEX data in row {row}")
continue
if row in note_rows:
playlist_notes[id] = row
else:
playlist_tracks[id] = row
playlist_notes[note_id] = row
# Database
for note in playlist.notes:
database_notes[note.id] = note.row
for track in playlist.tracks:
database_tracks[track.track_id] = track.row
# Notes to remove from playlist in database
for note_id in set(database_notes.keys()) - set(playlist_notes.keys()):
DEBUG(
f"_save_playlist(): Delete note.id={id} "
f"from playlist {playlist} in database"
)
Notes.delete_note(note_id)
# Tracks to remove from playlist in database
for track_id in (
set(database_tracks.keys()) - set(playlist_tracks.keys())
):
DEBUG(
f"_save_playlist(): Delete track.id={track_id} "
f"from playlist {playlist} in database"
)
PlaylistTracks.remove_track(playlist.id, track_id)
# Notes to add to playlist database
# Notes to add to database
# This should never be needed as notes are added to a specific
# playlist upon creation
for note_id in set(playlist_notes.keys()) - set(database_notes.keys()):
@ -827,10 +833,10 @@ class Playlist(QTableWidget):
f"missing from playlist {playlist} in database"
)
# Notes to remove from playlist database
# Notes to remove from database
for note_id in set(database_notes.keys()) - set(playlist_notes.keys()):
DEBUG(
f"_save_playlist(): Remove note.id={note_id} "
f"_save_playlist(): Delete note note_id={note_id} "
f"from playlist {playlist} in database"
)
Notes.delete_note(note_id)
@ -839,28 +845,58 @@ class Playlist(QTableWidget):
for note_id in set(playlist_notes.keys()) & set(database_notes.keys()):
if playlist_notes[note_id] != database_notes[note_id]:
DEBUG(
f"_save_playlist(): Set database note.id {note_id} "
f"row={playlist_notes[note_id]} "
f"in playlist {playlist} in database"
f"_save_playlist(): Update database note.id {note_id} "
f"from row={database_notes[note_id]} to "
f"row={playlist_notes[note_id]}"
)
Notes.update_note(note_id, playlist_notes[note_id])
# Track rows to update in playlist database
for track_id in (
# Now check tracks
# Create dictionaries indexed by row
playlist_tracks = {}
database_tracks = {}
# Playlist
for row in range(self.rowCount()):
if row in notes_rows:
continue
playlist_tracks[row] = self._get_row_id(row)
# Database
for track in playlist.tracks:
database_tracks[track.row] = track.track_id
# Tracks rows to add to database
for row in (
set(set(playlist_tracks.keys()) - set(database_tracks.keys()))
):
DEBUG(f"_save_playlist(): row {row} missing from database")
PlaylistTracks.add_track(self.playlist_id, playlist_tracks[row],
row)
# Track rows to remove from database
for row in (
set(database_tracks.keys()) - set(playlist_tracks.keys())
):
track = database_tracks[row]
DEBUG(
f"_save_playlist(): row {row} in database not playlist "
f"(track={track})"
)
PlaylistTracks.remove_track(playlist.id, row)
# Track rows to update in database
for row in (
set(playlist_tracks.keys()) & set(database_tracks.keys())
):
if playlist_tracks[track_id] != database_tracks[track_id]:
if playlist_tracks[row] != database_tracks[row]:
DEBUG(
f"_save_playlist(): Set database track.id {track_id} "
f"row={playlist_tracks[track_id]} "
f"in playlist {playlist} in database"
)
PlaylistTracks.update_track_row(
playlist_id=self.playlist_id,
track_id=track_id,
old_row=database_tracks[track_id],
new_row=playlist_tracks[track_id]
"_save_playlist(): Update row={row} in database for "
f"playlist {playlist} from track={database_tracks[row]} "
f"to track={playlist_tracks[row]}"
)
PlaylistTracks.update_row_track(
self.playlist_id, row, playlist_tracks[row])
def _set_row_bold(self, row, bold=True):
boldfont = QFont()