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):
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
def new(session, name):
DEBUG(f"Playlists.new(name={name})")
@ -253,6 +186,75 @@ class Playlists(Base):
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):
__tablename__ = 'playlisttracks'
@ -264,6 +266,13 @@ class PlaylistTracks(Base):
tracks = relationship("Tracks", back_populates="playlists")
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
def add_track(session, playlist_id, track_id, row):
DEBUG(
@ -296,24 +305,6 @@ class PlaylistTracks(Base):
record.row = new_row
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
def remove_track(session, playlist_id, row):
DEBUG(
@ -434,6 +425,13 @@ class Tracks(Base):
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
def get_path(session, id):
try:

View File

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

View File

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