Compare commits

..

No commits in common. "0cf649bb0173efb031e3557915a35876ac3f5455" and "1abe377b4cca308d96c6668eb4a2e81a51478394" have entirely different histories.

3 changed files with 174 additions and 159 deletions

View File

@ -166,73 +166,6 @@ class Playlists(Base):
def __repr__(self): def __repr__(self):
return (f"<Playlist(id={self.id}, name={self.name}>") return (f"<Playlist(id={self.id}, name={self.name}>")
def add_track(self, session, track, row=None):
"""
Add track to playlist at given row.
If row=None, add to end of playlist
"""
if not row:
row = PlaylistTracks.new_row(session, self.id)
glue = PlaylistTracks(row=row)
glue.track_id = track.id
self.tracks.append(glue)
session.commit()
def close(self, session):
"Record playlist as no longer loaded"
self.loaded = False
session.add(self)
session.commit()
@staticmethod
def get_all_closed_playlists(session):
"Returns a list of all playlists not currently open"
return (
session.query(Playlists)
.filter(
(Playlists.loaded == False) | # noqa E712
(Playlists.loaded == None)
)
.order_by(Playlists.last_used.desc())
).all()
@staticmethod
def get_all_playlists(session):
"Returns a list of all playlists"
return session.query(Playlists).all()
@staticmethod
def get_last_used(session):
"""
Return a list of playlists marked "loaded", ordered by loaded date.
"""
return (
session.query(Playlists)
.filter(Playlists.loaded == True) # noqa E712
.order_by(Playlists.last_used.desc())
).all()
def get_notes(self):
return [a.note for a in self.notes]
@staticmethod
def get_playlist(session, playlist_id):
return (
session.query(Playlists)
.filter(
Playlists.id == playlist_id # noqa E712
)
).one()
def get_tracks(self):
return [a.tracks for a in self.tracks]
@staticmethod @staticmethod
def new(session, name): def new(session, name):
DEBUG(f"Playlists.new(name={name})") DEBUG(f"Playlists.new(name={name})")
@ -253,6 +186,75 @@ class Playlists(Base):
return p return p
def close(self, session):
"Record playlist as no longer loaded"
self.loaded = False
session.add(self)
session.commit()
@staticmethod
def get_last_used(session):
"""
Return a list of playlists marked "loaded", ordered by loaded date.
"""
return (
session.query(Playlists)
.filter(Playlists.loaded == True) # noqa E712
.order_by(Playlists.last_used.desc())
).all()
@staticmethod
def get_all_playlists(session):
"Returns a list of all playlists"
return session.query(Playlists).all()
@staticmethod
def get_all_closed_playlists(session):
"Returns a list of all playlists not currently open"
return (
session.query(Playlists)
.filter(
(Playlists.loaded == False) | # noqa E712
(Playlists.loaded == None)
)
.order_by(Playlists.last_used.desc())
).all()
# Not currently used 1 June 2021
# @staticmethod
# def get_name(session, plid):
# """
# Return name of playlist with id 'plid'
# """
# return (
# session.query(Playlists.name)
# .filter(Playlists.id == plid)
# ).one()[0]
def add_track(self, session, track, row=None):
"""
Add track to playlist at given row.
If row=None, add to end of playlist
"""
if not row:
row = PlaylistTracks.new_row(session, self.id)
glue = PlaylistTracks(row=row)
glue.track_id = track.id
self.tracks.append(glue)
def get_notes(self):
return [a.note for a in self.notes]
def get_tracks(self):
return [a.tracks for a in self.tracks]
class PlaylistTracks(Base): class PlaylistTracks(Base):
__tablename__ = 'playlisttracks' __tablename__ = 'playlisttracks'
@ -264,6 +266,13 @@ class PlaylistTracks(Base):
tracks = relationship("Tracks", back_populates="playlists") tracks = relationship("Tracks", back_populates="playlists")
playlists = relationship("Playlists", back_populates="tracks") playlists = relationship("Playlists", back_populates="tracks")
@staticmethod
def new_row(session, playlist_id):
"Return row number > largest existing row number"
last_row = session.query(func.max(PlaylistTracks.row)).one()[0]
return last_row + 1
@staticmethod @staticmethod
def add_track(session, playlist_id, track_id, row): def add_track(session, playlist_id, track_id, row):
DEBUG( DEBUG(
@ -296,24 +305,6 @@ class PlaylistTracks(Base):
record.row = new_row record.row = new_row
session.commit() session.commit()
@staticmethod
def new_row(session, playlist_id):
"Return row number > largest existing row number"
last_row = session.query(func.max(PlaylistTracks.row)).one()[0]
return last_row + 1
@staticmethod
def remove_all_tracks(session, playlist_id):
"""
Remove all tracks from passed playlist_id
"""
session.query(PlaylistTracks).filter(
PlaylistTracks.playlist_id == playlist_id,
).delete()
session.commit()
@staticmethod @staticmethod
def remove_track(session, playlist_id, row): def remove_track(session, playlist_id, row):
DEBUG( DEBUG(
@ -434,6 +425,13 @@ class Tracks(Base):
return [a[0] for a in session.query(Tracks.path).all()] return [a[0] for a in session.query(Tracks.path).all()]
# Not used as of 1 June 2021
# @classmethod
# def get_all_tracks(cls):
# "Return a list of all tracks"
# return session.query(cls).all()
@staticmethod @staticmethod
def get_path(session, id): def get_path(session, id):
try: try:

View File

@ -217,7 +217,7 @@ class Window(QMainWindow, Ui_MainWindow):
# Get output filename # Get output filename
pathspec = QFileDialog.getSaveFileName( pathspec = QFileDialog.getSaveFileName(
self, 'Save Playlist', self, 'Save Playlist',
directory=f"{self.visible_playlist().name}.m3u", directory=f"{self.visible_playlist().db.name}.m3u",
filter="M3U files (*.m3u);;All files (*.*)" filter="M3U files (*.m3u);;All files (*.*)"
) )
if not pathspec: if not pathspec:
@ -227,23 +227,19 @@ class Window(QMainWindow, Ui_MainWindow):
if not path.endswith(".m3u"): if not path.endswith(".m3u"):
path += ".m3u" path += ".m3u"
with open(path, "w") as f:
# Get playlist db object # Required directive on first line
with Session() as session: f.write("#EXTM3U\n")
p = Playlists.get_playlist(session, self.visible_playlist().id) for track in self.visible_playlist().db.get_tracks():
with open(path, "w") as f: f.write(
# Required directive on first line "#EXTINF:"
f.write("#EXTM3U\n") f"{int(track.duration / 1000)},"
for track in p.get_tracks(): f"{track.title} - "
f.write( f"{track.artist}"
"#EXTINF:" "\n"
f"{int(track.duration / 1000)}," f"{track.path}"
f"{track.title} - " "\n"
f"{track.artist}" )
"\n"
f"{track.path}"
"\n"
)
def fade(self): def fade(self):
"Fade currently playing track" "Fade currently playing track"
@ -265,7 +261,7 @@ class Window(QMainWindow, Ui_MainWindow):
if ok: if ok:
with Session() as session: with Session() as session:
note = self.create_note(session, dlg.textValue()) note = self.create_note(session, dlg.textValue())
self.visible_playlist().add_note(session, note) self.visible_playlist().add_note(note)
def load_last_playlists(self): def load_last_playlists(self):
"Load the playlists that we loaded at end of last session" "Load the playlists that we loaded at end of last session"
@ -659,8 +655,7 @@ class DbDialog(QDialog):
item = self.ui.matchList.currentItem() item = self.ui.matchList.currentItem()
track_id = item.data(Qt.UserRole) track_id = item.data(Qt.UserRole)
with Session() as session: self.add_track(track_id)
self.add_track(session, track_id)
def add_selected_and_close(self): def add_selected_and_close(self):
self.add_selected() self.add_selected()
@ -683,14 +678,14 @@ class DbDialog(QDialog):
def double_click(self, entry): def double_click(self, entry):
track_id = entry.data(Qt.UserRole) track_id = entry.data(Qt.UserRole)
with Session() as session: self.add_track(track_id)
self.add_track(session, track_id)
# Select search text to make it easier for next search # Select search text to make it easier for next search
self.select_searchtext() self.select_searchtext()
def add_track(self, session, track_id): def add_track(self, track_id):
track = Tracks.track_from_id(session, track_id) with Session() as session:
self.parent().visible_playlist().add_to_playlist(session, track) track = Tracks.track_from_id(session, track_id)
self.parent().visible_playlist().add_to_playlist(session, track)
# Select search text to make it easier for next search # Select search text to make it easier for next search
self.select_searchtext() self.select_searchtext()

View File

@ -17,7 +17,7 @@ import os
from config import Config from config import Config
from datetime import datetime, timedelta from datetime import datetime, timedelta
from log import DEBUG, ERROR from log import DEBUG, ERROR
from model import Notes, Playlists, PlaylistTracks, Session, Settings, Tracks from model import Notes, PlaylistTracks, Session, Settings, Tracks
class Playlist(QTableWidget): class Playlist(QTableWidget):
@ -222,7 +222,7 @@ class Playlist(QTableWidget):
Notes object. Notes object.
""" """
DEBUG(f"playlists.add_to_playlist(session={session}, data={data})") DEBUG(f"add_to_playlist(session={session}, data={data})")
if isinstance(data, Tracks): if isinstance(data, Tracks):
self.add_track(session, data, repaint=repaint) self.add_track(session, data, repaint=repaint)
@ -241,13 +241,7 @@ class Playlist(QTableWidget):
row = self.currentRow() row = self.currentRow()
else: else:
row = self.rowCount() row = self.rowCount()
DEBUG(f"playlists.add_track(track={track}), row={row}") DEBUG(f"add_track(track={track}), row={row}")
# We need to add ourself to the session
us_in_db = session.query(Playlists).filter(
Playlists.id == self.id).one()
us_in_db.add_track(session, track, row)
self.insertRow(row) self.insertRow(row)
@ -279,13 +273,13 @@ class Playlist(QTableWidget):
"Clear current track" "Clear current track"
self._meta_clear_current() self._meta_clear_current()
self._repaint() self._repaint(save_playlist=False)
def clear_next(self): def clear_next(self):
"Clear next track" "Clear next track"
self._meta_clear_next() self._meta_clear_next()
self._repaint() self._repaint(save_playlist=False)
def get_next_track_id(self): def get_next_track_id(self):
"Return next track id" "Return next track id"
@ -325,7 +319,7 @@ class Playlist(QTableWidget):
for row in sorted(rows, reverse=True): for row in sorted(rows, reverse=True):
self.removeRow(row) self.removeRow(row)
self._repaint() self._repaint(save_playlist=False)
def get_selected_title(self): def get_selected_title(self):
"Return title of selected row or None" "Return title of selected row or None"
@ -352,24 +346,18 @@ class Playlist(QTableWidget):
scroll_to = self.item(current_row, self.COL_INDEX) scroll_to = self.item(current_row, self.COL_INDEX)
self.scrollToItem(scroll_to, QAbstractItemView.PositionAtCenter) self.scrollToItem(scroll_to, QAbstractItemView.PositionAtCenter)
next_track_id = self._mark_next_track() next_track_id = self._mark_next_track()
self._repaint() self._repaint(save_playlist=False)
return next_track_id return next_track_id
def play_stopped(self): def play_stopped(self):
self._meta_clear_current() self._meta_clear_current()
self.current_track_start_time = None self.current_track_start_time = None
self._repaint() self._repaint(save_playlist=False)
def populate(self, session): def populate(self, session):
# add tracks and notes in row order. # add them in row order. We don't mandate that an item will be
# We don't mandate that an item will be
# on its specified row, only that it will be above # on its specified row, only that it will be above
# larger-numbered row items, and below lower-numbered ones. # larger-numbered row items, and below lower-numbered ones.
# First, save our id for the future
self.id = self.db.id
self.name = self.db.name
data = [] data = []
for t in self.db.tracks: for t in self.db.tracks:
@ -393,7 +381,7 @@ class Playlist(QTableWidget):
def repaint(self): def repaint(self):
# Called when we change tabs # Called when we change tabs
self._repaint() self._repaint(save_playlist=False)
def select_next_track(self): def select_next_track(self):
""" """
@ -521,10 +509,10 @@ class Playlist(QTableWidget):
if row in self._meta_get_notes(): if row in self._meta_get_notes():
Notes.delete_note(session, id) Notes.delete_note(session, id)
else: else:
PlaylistTracks.remove_track(session, self.id, row) PlaylistTracks.remove_track(session, self.db.id, row)
self.removeRow(row) self.removeRow(row)
self._save_playlist(session) self._save_playlist(session)
self._repaint() self._repaint()
def _drop_on(self, event): def _drop_on(self, event):
@ -708,7 +696,7 @@ class Playlist(QTableWidget):
else: else:
title = "" title = ""
DEBUG( DEBUG(
f"playlist[{self.id}:{self.name}]._meta_set(row={row}, " f"playlist[{self.db.id}:{self.db.name}]._meta_set(row={row}, "
f"title={title}, metadata={metadata})" f"title={title}, metadata={metadata})"
) )
if row is None: if row is None:
@ -730,14 +718,14 @@ class Playlist(QTableWidget):
track_id = self._get_row_id(row) track_id = self._get_row_id(row)
if track_id: if track_id:
self._meta_set_next(self.currentRow()) self._meta_set_next(self.currentRow())
self._repaint() self._repaint(save_playlist=False)
self.master_process.set_next_track(track_id) self.master_process.set_next_track(track_id)
def _repaint(self, clear_selection=True): def _repaint(self, clear_selection=True):
"Set row colours, fonts, etc" "Set row colours, fonts, etc"
DEBUG( DEBUG(
f"playlist[{self.id}:{self.name}]." f"playlist[{self.db.id}:{self.db.name}]."
f"_repaint(clear_selection={clear_selection}" f"_repaint(clear_selection={clear_selection}"
) )
@ -822,21 +810,15 @@ class Playlist(QTableWidget):
def _save_playlist(self, session): def _save_playlist(self, session):
""" """
Save playlist to database. Save playlist to database. We do this by correcting differences
between the on-screen (definitive) playlist and that in the
database.
For notes: check the database entry is correct and update it if We treat the notes rows and the tracks rows differently. Notes must
necessary. Playlists:Note is one:many, so there is only one notes appear only once in only one playlist. Tracks can appear multiple
appearance in all playlists. times in one playlist and in multiple playlists.
For tracks: erase the playlist tracks and recreate. This is much
simpler than trying to correct any Playlists:Tracks many:many
errors.
""" """
# We need to add ourself to the session
us_in_db = session.query(Playlists).filter(
Playlists.id == self.id).one()
# Notes first # Notes first
# Create dictionaries indexed by note_id # Create dictionaries indexed by note_id
playlist_notes = {} playlist_notes = {}
@ -852,7 +834,7 @@ class Playlist(QTableWidget):
playlist_notes[note_id] = row playlist_notes[note_id] = row
# Database # Database
for note in us_in_db.notes: for note in self.db.notes:
database_notes[note.id] = note.row database_notes[note.id] = note.row
# Notes to add to database # Notes to add to database
@ -861,14 +843,14 @@ class Playlist(QTableWidget):
for note_id in set(playlist_notes.keys()) - set(database_notes.keys()): for note_id in set(playlist_notes.keys()) - set(database_notes.keys()):
ERROR( ERROR(
f"_save_playlist(): Note.id={note_id} " f"_save_playlist(): Note.id={note_id} "
f"missing from playlist {us_in_db} in database" f"missing from playlist {self} in database"
) )
# Notes to remove from database # Notes to remove from database
for note_id in set(database_notes.keys()) - set(playlist_notes.keys()): for note_id in set(database_notes.keys()) - set(playlist_notes.keys()):
DEBUG( DEBUG(
f"_save_playlist(): Delete note note_id={note_id} " f"_save_playlist(): Delete note note_id={note_id} "
f"from playlist {us_in_db} in database" f"from playlist {self} in database"
) )
Notes.delete_note(session, note_id) Notes.delete_note(session, note_id)
@ -882,15 +864,55 @@ class Playlist(QTableWidget):
) )
Notes.update_note(session, note_id, playlist_notes[note_id]) Notes.update_note(session, note_id, playlist_notes[note_id])
# Tracks # Now check tracks
# Remove all tracks for us in datbase # Create dictionaries indexed by row
PlaylistTracks.remove_all_tracks(session, self.id) playlist_tracks = {}
# Iterate on-screen playlist and add tracks back in database_tracks = {}
# Playlist
for row in range(self.rowCount()): for row in range(self.rowCount()):
if row in notes_rows: if row in notes_rows:
continue continue
PlaylistTracks.add_track( playlist_tracks[row] = self._get_row_id(row)
session, self.id, self._get_row_id(row), row)
# Database
# Workaround for issue #10
# for track in self.db.tracks:
for track in session.query(PlaylistTracks).filter(
PlaylistTracks.playlist_id == self.db.id).all():
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(session, self.db.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(session, self.db.id, row)
# Track rows to update in database
for row in (
set(playlist_tracks.keys()) & set(database_tracks.keys())
):
if playlist_tracks[row] != database_tracks[row]:
DEBUG(
"_save_playlist(): Update row={row} in database for "
f"playlist {self} from track={database_tracks[row]} "
f"to track={playlist_tracks[row]}"
)
PlaylistTracks.update_row_track(
session, self.db.id, row, playlist_tracks[row])
def _set_column_widths(self): def _set_column_widths(self):