From 4984ddec988201aa59520dea36ecbdbf02508996 Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Wed, 2 Jun 2021 08:26:54 +0100 Subject: [PATCH] Use sessions correctly (fixes #5) --- app/model.py | 97 ++++++++------- app/musicmuster.py | 290 ++++++++++++++++++++++++--------------------- app/playlists.py | 208 ++++++++++++++++---------------- app/songdb.py | 42 +++---- 4 files changed, 337 insertions(+), 300 deletions(-) diff --git a/app/model.py b/app/model.py index efa1e46..6e7d632 100644 --- a/app/model.py +++ b/app/model.py @@ -31,9 +31,8 @@ engine = sqlalchemy.create_engine(f"{Config.MYSQL_CONNECT}?charset=utf8", Base = declarative_base() Base.metadata.create_all(engine) -# Create a Session +# Create a Session factory Session = sessionmaker(bind=engine) -session = Session() # Database classes @@ -52,7 +51,7 @@ class Notes(Base): ) @staticmethod - def add_note(playlist_id, row, text): + def add_note(session, playlist_id, row, text): DEBUG(f"add_note(playlist_id={playlist_id}, row={row}, text={text})") note = Notes() note.playlist_id = playlist_id @@ -63,18 +62,19 @@ class Notes(Base): return note @staticmethod - def delete_note(id): + def delete_note(session, id): DEBUG(f"delete_note(id={id}") session.query(Notes).filter(Notes.id == id).delete() session.commit() - @staticmethod - def get_note(id): - return session.query(Notes).filter(Notes.id == id).one() + # Not currently used 1 June 2021 + # @staticmethod + # def get_note(session, id): + # return session.query(Notes).filter(Notes.id == id).one() @classmethod - def update_note(cls, id, row, text=None): + def update_note(cls, session, id, row, text=None): """ Update note details. If text=None, don't change text. """ @@ -97,7 +97,7 @@ class Playdates(Base): tracks = relationship("Tracks", back_populates="playdates") @staticmethod - def add_playdate(track): + def add_playdate(session, track): DEBUG(f"add_playdate(track={track})") pd = Playdates() pd.lastplayed = datetime.now() @@ -167,7 +167,7 @@ class Playlists(Base): return (f"") @staticmethod - def new(name): + def new(session, name): DEBUG(f"Playlists.new(name={name})") playlist = Playlists() playlist.name = name @@ -176,7 +176,7 @@ class Playlists(Base): return playlist @staticmethod - def open(plid): + def open(session, plid): "Record playlist as loaded and used now" p = session.query(Playlists).filter(Playlists.id == plid).one() @@ -186,7 +186,7 @@ class Playlists(Base): return p - def close(self): + def close(self, session): "Record playlist as no longer loaded" self.loaded = False @@ -194,7 +194,7 @@ class Playlists(Base): session.commit() @staticmethod - def get_last_used(): + def get_last_used(session): """ Return a list of playlists marked "loaded", ordered by loaded date. """ @@ -206,13 +206,13 @@ class Playlists(Base): ).all() @staticmethod - def get_all_playlists(): + def get_all_playlists(session): "Returns a list of all playlists" return session.query(Playlists).all() @staticmethod - def get_all_closed_playlists(): + def get_all_closed_playlists(session): "Returns a list of all playlists not currently open" return ( @@ -224,25 +224,26 @@ class Playlists(Base): .order_by(Playlists.last_used.desc()) ).all() - @staticmethod - def get_name(plid): - """ - Return name of playlist with id 'plid' - """ + # 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] + # return ( + # session.query(Playlists.name) + # .filter(Playlists.id == plid) + # ).one()[0] - def add_track(self, track, row=None): + 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(self.id) + row = PlaylistTracks.new_row(session, self.id) glue = PlaylistTracks(row=row) glue.track_id = track.id @@ -266,14 +267,14 @@ class PlaylistTracks(Base): playlists = relationship("Playlists", back_populates="tracks") @staticmethod - def new_row(playlist_id): + 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(playlist_id, track_id, row): + def add_track(session, playlist_id, track_id, row): DEBUG( f"PlaylistTracks.add_track(playlist_id={playlist_id}, " f"track_id={track_id}, row={row})" @@ -286,7 +287,7 @@ class PlaylistTracks(Base): session.commit() @staticmethod - def move_track(from_playlist_id, row, to_playlist_id): + def move_track(session, from_playlist_id, row, to_playlist_id): DEBUG( "PlaylistTracks.move_tracks(from_playlist_id=" f"{from_playlist_id}, row={row}, " @@ -305,7 +306,7 @@ class PlaylistTracks(Base): session.commit() @staticmethod - def remove_track(playlist_id, row): + def remove_track(session, playlist_id, row): DEBUG( f"PlaylistTracks.remove_track(playlist_id={playlist_id}, " f"row={row})" @@ -317,7 +318,7 @@ class PlaylistTracks(Base): session.commit() @staticmethod - def update_row_track(playlist_id, row, track_id): + def update_row_track(session, playlist_id, row, track_id): DEBUG( f"PlaylistTracks.update_track_row(playlist_id={playlist_id}, " f"row={row}, track_id={track_id})" @@ -335,6 +336,13 @@ class PlaylistTracks(Base): f"PlaylistTracks.row == {row}" ) return + except NoResultFound: + ERROR( + f"No rows matched in query: " + f"PlaylistTracks.playlist_id == {playlist_id}, " + f"PlaylistTracks.row == {row}" + ) + return plt.track_id = track_id session.commit() @@ -350,7 +358,7 @@ class Settings(Base): f_string = Column(String(128), default=None, nullable=True) @classmethod - def get_int(cls, name): + def get_int(cls, session, name): try: int_setting = session.query(cls).filter( cls.name == name).one() @@ -362,7 +370,7 @@ class Settings(Base): session.commit() return int_setting - def update(self, data): + def update(self, session, data): for key, value in data.items(): assert hasattr(self, key) setattr(self, key, value) @@ -392,7 +400,7 @@ class Tracks(Base): ) @classmethod - def get_or_create(cls, path): + def get_or_create(cls, session, path): DEBUG(f"Tracks.get_or_create(path={path})") try: track = session.query(cls).filter(cls.path == path).one() @@ -403,7 +411,7 @@ class Tracks(Base): return track @staticmethod - def get_duration(id): + def get_duration(session, id): try: return session.query( Tracks.duration).filter(Tracks.id == id).one()[0] @@ -412,19 +420,20 @@ class Tracks(Base): return None @staticmethod - def get_all_paths(): + def get_all_paths(session): "Return a list of paths of all tracks" return [a[0] for a in session.query(Tracks.path).all()] - @classmethod - def get_all_tracks(cls): - "Return a list of all tracks" + # Not used as of 1 June 2021 + # @classmethod + # def get_all_tracks(cls): + # "Return a list of all tracks" - return session.query(cls).all() + # return session.query(cls).all() @staticmethod - def get_path(id): + def get_path(session, id): try: return session.query(Tracks.path).filter(Tracks.id == id).one()[0] except NoResultFound: @@ -432,7 +441,7 @@ class Tracks(Base): return None @staticmethod - def get_track(id): + def get_track(session, id): try: DEBUG(f"Tracks.get_track(track_id={id})") track = session.query(Tracks).filter(Tracks.id == id).one() @@ -442,7 +451,7 @@ class Tracks(Base): return None @staticmethod - def search_titles(text): + def search_titles(session, text): return ( session.query(Tracks) .filter(Tracks.title.ilike(f"%{text}%")) @@ -450,7 +459,7 @@ class Tracks(Base): ).all() @staticmethod - def track_from_id(id): + def track_from_id(session, id): return session.query(Tracks).filter( Tracks.id == id).one() diff --git a/app/musicmuster.py b/app/musicmuster.py index 1216fec..4e3c3ce 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -25,7 +25,8 @@ import helpers import music from config import Config -from model import Notes, Playdates, Playlists, PlaylistTracks, Settings, Tracks +from model import (Notes, Playdates, Playlists, PlaylistTracks, + Session, Settings, Tracks) from playlists import Playlist from songdb import add_path_to_db from ui.dlg_search_database_ui import Ui_Dialog @@ -70,21 +71,23 @@ class Window(QMainWindow, Ui_MainWindow): dlg.setNameFilter("Music files (*.flac *.mp3)") if dlg.exec_(): - for fname in dlg.selectedFiles(): - track = add_path_to_db(fname) - self.visible_playlist()._add_to_playlist(track) + with Session() as session: + for fname in dlg.selectedFiles(): + track = add_path_to_db(session, fname) + self.visible_playlist()._add_to_playlist(track) def set_main_window_size(self): - record = Settings.get_int("mainwindow_x") - x = record.f_int or 1 - record = Settings.get_int("mainwindow_y") - y = record.f_int or 1 - record = Settings.get_int("mainwindow_width") - width = record.f_int or 1599 - record = Settings.get_int("mainwindow_height") - height = record.f_int or 981 - self.setGeometry(x, y, width, height) + with Session() as session: + record = Settings.get_int(session, "mainwindow_x") + x = record.f_int or 1 + record = Settings.get_int(session, "mainwindow_y") + y = record.f_int or 1 + record = Settings.get_int(session, "mainwindow_width") + width = record.f_int or 1599 + record = Settings.get_int(session, "mainwindow_height") + height = record.f_int or 981 + self.setGeometry(x, y, width, height) def clear_selection(self): if self.visible_playlist(): @@ -100,21 +103,22 @@ class Window(QMainWindow, Ui_MainWindow): else: DEBUG("closeEvent() accepted") - record = Settings.get_int("mainwindow_height") - if record.f_int != self.height(): - record.update({'f_int': self.height()}) + with Session() as session: + record = Settings.get_int(session, "mainwindow_height") + if record.f_int != self.height(): + record.update(session, {'f_int': self.height()}) - record = Settings.get_int("mainwindow_width") - if record.f_int != self.width(): - record.update({'f_int': self.width()}) + record = Settings.get_int(session, "mainwindow_width") + if record.f_int != self.width(): + record.update(session, {'f_int': self.width()}) - record = Settings.get_int("mainwindow_x") - if record.f_int != self.x(): - record.update({'f_int': self.x()}) + record = Settings.get_int(session, "mainwindow_x") + if record.f_int != self.x(): + record.update(session, {'f_int': self.x()}) - record = Settings.get_int("mainwindow_y") - if record.f_int != self.y(): - record.update({'f_int': self.y()}) + record = Settings.get_int(session, "mainwindow_y") + if record.f_int != self.y(): + record.update(session, {'f_int': self.y()}) event.accept() @@ -159,8 +163,9 @@ class Window(QMainWindow, Ui_MainWindow): dlg.resize(500, 100) ok = dlg.exec() if ok: - playlist = Playlists.new(dlg.textValue()) - self.load_playlist(playlist) + with Session() as session: + playlist = Playlists.new(session, dlg.textValue()) + self.load_playlist(session, playlist) def change_volume(self, volume): "Change player maximum volume" @@ -170,11 +175,12 @@ class Window(QMainWindow, Ui_MainWindow): self.music.set_volume(volume) def close_playlist(self): - self.visible_playlist().db.close() - index = self.tabPlaylist.currentIndex() - self.tabPlaylist.removeTab(index) + with Session() as session: + self.visible_playlist().db.close(session) + index = self.tabPlaylist.currentIndex() + self.tabPlaylist.removeTab(index) - def create_note(self, text): + def create_note(self, session, text): """ Create note @@ -189,7 +195,10 @@ class Window(QMainWindow, Ui_MainWindow): row = self.visible_playlist().rowCount() DEBUG(f"musicmuster.create_note(text={text}): row={row}") - return Notes.add_note(self.visible_playlist().db.id, row, text) + note = Notes.add_note( + session, self.visible_playlist().db.id, row, text) + + return note def disable_play_next_controls(self): DEBUG("disable_play_next_controls()") @@ -250,19 +259,21 @@ class Window(QMainWindow, Ui_MainWindow): dlg.resize(500, 100) ok = dlg.exec() if ok: - note = self.create_note(dlg.textValue()) - self.visible_playlist().add_note(note) + with Session() as session: + note = self.create_note(session, dlg.textValue()) + self.visible_playlist().add_note(note) def load_last_playlists(self): "Load the playlists that we loaded at end of last session" - for playlist in Playlists.get_last_used(): - DEBUG( - f"load_last_playlists(), playlist.name={playlist.name}, " - f"playlist.id={playlist.id}") - self.load_playlist(playlist) + with Session() as session: + for playlist in Playlists.get_last_used(session): + DEBUG( + f"load_last_playlists(), playlist.name={playlist.name}, " + f"playlist.id={playlist.id}") + self.load_playlist(session, playlist) - def load_playlist(self, playlist_db): + def load_playlist(self, session, playlist_db): """ Take the passed database object, create a playlist display, attach the database object, get it populated and then add tab. @@ -270,40 +281,43 @@ class Window(QMainWindow, Ui_MainWindow): playlist_table = Playlist(self) playlist_table.db = playlist_db - playlist_table.populate() + playlist_table.populate(session) idx = self.tabPlaylist.addTab(playlist_table, playlist_db.name) self.tabPlaylist.setCurrentIndex(idx) def move_selected(self): "Move selected rows to another playlist" - playlists = list( - set(Playlists.get_all_playlists()) - {self.visible_playlist().db} - ) - dlg = SelectPlaylistDialog(self, playlists=playlists) - dlg.exec() - if not dlg.plid: - return + with Session() as session: + playlists = list( + set(Playlists.get_all_playlists(session)) - + {self.visible_playlist().db} + ) + dlg = SelectPlaylistDialog(self, playlists=playlists) + dlg.exec() + if not dlg.plid: + return - # If destination playlist is visible, we need to add the moved - # tracks to it. If not, they will be automatically loaded when - # the playlistis opened. - destination_playlist = None - for tab in range(self.tabPlaylist.count()): - if self.tabPlaylist.widget(tab).db.id == dlg.plid: - destination_playlist = self.tabPlaylist.widget(tab) - break + # If destination playlist is visible, we need to add the moved + # tracks to it. If not, they will be automatically loaded when + # the playlistis opened. + destination_playlist = None + for tab in range(self.tabPlaylist.count()): + if self.tabPlaylist.widget(tab).db.id == dlg.plid: + destination_playlist = self.tabPlaylist.widget(tab) + break - rows = [] - for (row, track_id) in ( - self.visible_playlist().get_selected_rows_and_tracks()): - rows.append(row) - # Update database - PlaylistTracks.move_track( - self.visible_playlist().db.id, row, dlg.plid) - # Update destination playlist if visible - if destination_playlist: - destination_playlist.add_track(Tracks.track_from_id(track_id)) + rows = [] + for (row, track_id) in ( + self.visible_playlist().get_selected_rows_and_tracks()): + rows.append(row) + # Update database + PlaylistTracks.move_track( + session, self.visible_playlist().db.id, row, dlg.plid) + # Update destination playlist if visible + if destination_playlist: + destination_playlist.add_track( + session, Tracks.track_from_id(session, track_id)) # Update source playlist self.visible_playlist().remove_rows(rows) @@ -334,46 +348,47 @@ class Window(QMainWindow, Ui_MainWindow): f"{self.current_track.title if self.current_track else None}" ) - # Stop current track, if any - self.stop_playing() + with Session() as session: + # Stop current track, if any + self.stop_playing() - # Play next track - self.current_track = self.next_track - self.current_track_playlist = self.next_track_playlist - self.next_track = None - self.next_track_playlist = None - self.music.play(self.current_track.path) + # Play next track + self.current_track = self.next_track + self.current_track_playlist = self.next_track_playlist + self.next_track = None + self.next_track_playlist = None + self.music.play(self.current_track.path) - # Update metadata - next_track_id = self.current_track_playlist.play_started() + # Update metadata + next_track_id = self.current_track_playlist.play_started() - if next_track_id is not None: - self.next_track = Tracks.get_track(next_track_id) - self.next_track_playlist = self.current_track_playlist - # Check we can read it - if not os.access(self.next_track.path, os.R_OK): - self.show_warning( - "Can't read next track", - self.next_track.path) - else: - self.next_track = self.next_track_playlist = None + if next_track_id is not None: + self.next_track = Tracks.get_track(session, next_track_id) + self.next_track_playlist = self.current_track_playlist + # Check we can read it + if not os.access(self.next_track.path, os.R_OK): + self.show_warning( + "Can't read next track", + self.next_track.path) + else: + self.next_track = self.next_track_playlist = None - # Tell database to record it as played - self.current_track.update_lastplayed() - Playdates.add_playdate(self.current_track) + # Tell database to record it as played + self.current_track.update_lastplayed() + Playdates.add_playdate(session, self.current_track) - self.disable_play_next_controls() - self.update_headers() + self.disable_play_next_controls() + self.update_headers() - # Set time clocks - now = datetime.now() - self.label_start_tod.setText(now.strftime("%H:%M:%S")) - silence_at = self.current_track.silence_at - silence_time = now + timedelta(milliseconds=silence_at) - self.label_silent_tod.setText(silence_time.strftime("%H:%M:%S")) - self.label_fade_length.setText(helpers.ms_to_mmss( - silence_at - self.current_track.fade_at - )) + # Set time clocks + now = datetime.now() + self.label_start_tod.setText(now.strftime("%H:%M:%S")) + silence_at = self.current_track.silence_at + silence_time = now + timedelta(milliseconds=silence_at) + self.label_silent_tod.setText(silence_time.strftime("%H:%M:%S")) + self.label_fade_length.setText(helpers.ms_to_mmss( + silence_at - self.current_track.fade_at + )) def play_previous(self): "Resume playing last track" @@ -386,12 +401,13 @@ class Window(QMainWindow, Ui_MainWindow): dlg.exec() def open_playlist(self): - playlists = Playlists.get_all_closed_playlists() - dlg = SelectPlaylistDialog(self, playlists=playlists) - dlg.exec() - if dlg.plid: - playlist = Playlists.open(dlg.plid) - self.load_playlist(playlist) + with Session() as session: + playlists = Playlists.get_all_closed_playlists(session) + dlg = SelectPlaylistDialog(self, playlists=playlists) + dlg.exec() + if dlg.plid: + playlist = Playlists.open(session, dlg.plid) + self.load_playlist(session, playlist) def select_next_track(self): "Select next or first track in playlist" @@ -406,15 +422,16 @@ class Window(QMainWindow, Ui_MainWindow): def set_next_track(self, next_track_id=None): "Set selected track as next" - if not next_track_id: - next_track_id = self.visible_playlist().set_selected_as_next() - if next_track_id: - if self.next_track_playlist != self.visible_playlist(): - if self.next_track_playlist: - self.next_track_playlist.clear_next() - self.next_track_playlist = self.visible_playlist() - self.next_track = Tracks.get_track(next_track_id) - self.update_headers() + with Session() as session: + if not next_track_id: + next_track_id = self.visible_playlist().set_selected_as_next() + if next_track_id: + if self.next_track_playlist != self.visible_playlist(): + if self.next_track_playlist: + self.next_track_playlist.clear_next() + self.next_track_playlist = self.visible_playlist() + self.next_track = Tracks.get_track(session, next_track_id) + self.update_headers() def show_warning(self, title, msg): "Display a warning to user" @@ -605,6 +622,7 @@ class Window(QMainWindow, Ui_MainWindow): class DbDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) + self.session = Session() self.ui = Ui_Dialog() self.ui.setupUi(self) self.ui.searchString.textEdited.connect(self.chars_typed) @@ -614,20 +632,20 @@ class DbDialog(QDialog): self.ui.btnClose.clicked.connect(self.close) self.ui.matchList.itemSelectionChanged.connect(self.selection_changed) - record = Settings.get_int("dbdialog_width") + record = Settings.get_int(self.session, "dbdialog_width") width = record.f_int or 800 - record = Settings.get_int("dbdialog_height") + record = Settings.get_int(self.session, "dbdialog_height") height = record.f_int or 600 self.resize(width, height) def __del__(self): - record = Settings.get_int("dbdialog_height") + record = Settings.get_int(self.session, "dbdialog_height") if record.f_int != self.height(): - record.update({'f_int': self.height()}) + record.update(self.session, {'f_int': self.height()}) - record = Settings.get_int("dbdialog_width") + record = Settings.get_int(self.session, "dbdialog_width") if record.f_int != self.width(): - record.update({'f_int': self.width()}) + record.update(self.session, {'f_int': self.width()}) def add_selected(self): if not self.ui.matchList.selectedItems(): @@ -643,7 +661,7 @@ class DbDialog(QDialog): def chars_typed(self, s): if len(s) >= 3: - matches = Tracks.search_titles(s) + matches = Tracks.search_titles(self.session, s) self.ui.matchList.clear() if matches: for track in matches: @@ -662,7 +680,7 @@ class DbDialog(QDialog): self.select_searchtext() def add_track(self, track_id): - track = Tracks.track_from_id(track_id) + track = Tracks.track_from_id(self.session, track_id) self.parent().visible_playlist()._add_to_playlist(track) # Select search text to make it easier for next search self.select_searchtext() @@ -677,7 +695,7 @@ class DbDialog(QDialog): item = self.ui.matchList.currentItem() track_id = item.data(Qt.UserRole) - self.ui.dbPath.setText(Tracks.get_path(track_id)) + self.ui.dbPath.setText(Tracks.get_path(self.session, track_id)) class SelectPlaylistDialog(QDialog): @@ -693,11 +711,12 @@ class SelectPlaylistDialog(QDialog): self.ui.buttonBox.rejected.connect(self.close) self.plid = None - record = Settings.get_int("select_playlist_dialog_width") - width = record.f_int or 800 - record = Settings.get_int("select_playlist_dialog_height") - height = record.f_int or 600 - self.resize(width, height) + with Session() as session: + record = Settings.get_int(session, "select_playlist_dialog_width") + width = record.f_int or 800 + record = Settings.get_int(session, "select_playlist_dialog_height") + height = record.f_int or 600 + self.resize(width, height) for (plid, plname) in [(a.id, a.name) for a in playlists]: p = QListWidgetItem() @@ -706,13 +725,14 @@ class SelectPlaylistDialog(QDialog): self.ui.lstPlaylists.addItem(p) def __del__(self): - record = Settings.get_int("select_playlist_dialog_height") - if record.f_int != self.height(): - record.update({'f_int': self.height()}) + with Session() as session: + record = Settings.get_int(session, "select_playlist_dialog_height") + if record.f_int != self.height(): + record.update(session, {'f_int': self.height()}) - record = Settings.get_int("select_playlist_dialog_width") - if record.f_int != self.width(): - record.update({'f_int': self.width()}) + record = Settings.get_int(session, "select_playlist_dialog_width") + if record.f_int != self.width(): + record.update(session, {'f_int': self.width()}) def list_doubleclick(self, entry): self.plid = entry.data(Qt.UserRole) diff --git a/app/playlists.py b/app/playlists.py index b33b733..aad3ead 100644 --- a/app/playlists.py +++ b/app/playlists.py @@ -17,7 +17,7 @@ import os from config import Config from datetime import datetime, timedelta from log import DEBUG, ERROR -from model import Notes, PlaylistTracks, Settings, Tracks +from model import Notes, PlaylistTracks, Session, Settings, Tracks class Playlist(QTableWidget): @@ -94,9 +94,10 @@ class Playlist(QTableWidget): for column in range(self.columnCount()): width = self.columnWidth(column) name = f"playlist_col_{str(column)}_width" - record = Settings.get_int(name) - if record.f_int != self.columnWidth(column): - record.update({'f_int': width}) + with Session() as session: + record = Settings.get_int(session, name) + if record.f_int != self.columnWidth(column): + record.update(session, {'f_int': width}) event.accept() @@ -336,7 +337,7 @@ class Playlist(QTableWidget): self.current_track_start_time = None self._repaint(save_playlist=False) - def populate(self): + def populate(self, session): # 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. @@ -454,7 +455,7 @@ class Playlist(QTableWidget): elif isinstance(data, Notes): self.add_note(data, repaint=repaint) - def _calculate_next_start_time(self, row, start): + def _calculate_next_start_time(self, session, row, start): "Return this row's end time given its start time" if start is None: @@ -463,7 +464,7 @@ class Playlist(QTableWidget): DEBUG("_calculate_next_start_time() called with row=None") return None - duration = Tracks.get_duration(self._get_row_id(row)) + duration = Tracks.get_duration(session, self._get_row_id(row)) return start + timedelta(milliseconds=duration) def _can_read_track(self, track): @@ -499,12 +500,13 @@ class Playlist(QTableWidget): msg.setDefaultButton(QMessageBox.Cancel) msg.setWindowTitle("Delete row") if msg.exec() == QMessageBox.Yes: - id = self._get_row_id(row) - if row in self._meta_get_notes(): - Notes.delete_note(id) - else: - PlaylistTracks.remove_track(self.db.id, row) - self.removeRow(row) + with Session() as session: + id = self._get_row_id(row) + if row in self._meta_get_notes(): + Notes.delete_note(session, id) + else: + PlaylistTracks.remove_track(session, self.db.id, row) + self.removeRow(row) self._repaint() @@ -723,86 +725,88 @@ class Playlist(QTableWidget): f"save_playlist={save_playlist})" ) - if clear_selection: - self.clearSelection() - if save_playlist: - self._save_playlist() + with Session() as session: + if clear_selection: + self.clearSelection() + if save_playlist: + self._save_playlist(session) - current = self._meta_get_current() - next = self._meta_get_next() - notes = self._meta_get_notes() + current = self._meta_get_current() + next = self._meta_get_next() + notes = self._meta_get_notes() - # Set colours and start times - next_start_time = None + # Set colours and start times + next_start_time = None - # Cycle through all rows - for row in range(self.rowCount()): - # We can't calculate start times until next_start_time is - # set. That can be set by either a note with a time, or the - # current track. + # Cycle through all rows + for row in range(self.rowCount()): + # We can't calculate start times until next_start_time is + # set. That can be set by either a note with a time, or the + # current track. - if row in notes: - row_time = self._get_row_time(row) - if row_time: - next_start_time = row_time - # Set colour - self._set_row_colour( - row, QColor(Config.COLOUR_NOTES_PLAYLIST) - ) - self._set_row_bold(row) - - elif row == current: - # Set start time - self._set_row_time(row, self.current_track_start_time) - # Calculate next_start_time - next_start_time = self._calculate_next_start_time( - row, self.current_track_start_time) - # Set colour - self._set_row_colour(row, QColor( - Config.COLOUR_CURRENT_PLAYLIST)) - # Make bold - self._set_row_bold(row) - - elif row == next: - # if there's a current track playing, set start time from that - if self.current_track_start_time: - start_time = self._calculate_next_start_time( - current, self.current_track_start_time) - else: - # No current track to base from, but don't change - # time if it's already set - start_time = self._get_row_time(row) - if not start_time: - start_time = next_start_time - # Now set it - self._set_row_time(row, start_time) - next_start_time = self._calculate_next_start_time( - row, start_time) - # Set colour - self._set_row_colour(row, QColor(Config.COLOUR_NEXT_PLAYLIST)) - # Make bold - self._set_row_bold(row) - - else: - # Stripe remaining rows - if row % 2: - colour = QColor(Config.COLOUR_ODD_PLAYLIST) - else: - colour = QColor(Config.COLOUR_EVEN_PLAYLIST) - self._set_row_colour(row, colour) - - 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 - if next_start_time: - self._set_row_time(row, next_start_time) - next_start_time = self._calculate_next_start_time( - row, next_start_time) - # Don't dim unplayed tracks + if row in notes: + row_time = self._get_row_time(row) + if row_time: + next_start_time = row_time + # Set colour + self._set_row_colour( + row, QColor(Config.COLOUR_NOTES_PLAYLIST) + ) self._set_row_bold(row) - def _save_playlist(self): + elif row == current: + # Set start time + self._set_row_time(row, self.current_track_start_time) + # Calculate next_start_time + next_start_time = self._calculate_next_start_time( + session, row, self.current_track_start_time) + # Set colour + self._set_row_colour(row, QColor( + Config.COLOUR_CURRENT_PLAYLIST)) + # Make bold + self._set_row_bold(row) + + elif row == next: + # if there's a track playing, set start time from that + if self.current_track_start_time: + start_time = self._calculate_next_start_time( + session, current, self.current_track_start_time) + else: + # No current track to base from, but don't change + # time if it's already set + start_time = self._get_row_time(row) + if not start_time: + start_time = next_start_time + # Now set it + self._set_row_time(row, start_time) + next_start_time = self._calculate_next_start_time( + session, row, start_time) + # Set colour + self._set_row_colour( + row, QColor(Config.COLOUR_NEXT_PLAYLIST)) + # Make bold + self._set_row_bold(row) + + else: + # Stripe remaining rows + if row % 2: + colour = QColor(Config.COLOUR_ODD_PLAYLIST) + else: + colour = QColor(Config.COLOUR_EVEN_PLAYLIST) + self._set_row_colour(row, colour) + + 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 + if next_start_time: + self._set_row_time(row, next_start_time) + next_start_time = self._calculate_next_start_time( + session, row, next_start_time) + # Don't dim unplayed tracks + self._set_row_bold(row) + + def _save_playlist(self, session): """ Save playlist to database. We do this by correcting differences between the on-screen (definitive) playlist and that in the @@ -846,7 +850,7 @@ class Playlist(QTableWidget): f"_save_playlist(): Delete note note_id={note_id} " f"from playlist {self} in database" ) - Notes.delete_note(note_id) + Notes.delete_note(session, note_id) # Note rows to update in playlist database for note_id in set(playlist_notes.keys()) & set(database_notes.keys()): @@ -856,7 +860,7 @@ class Playlist(QTableWidget): f"from row={database_notes[note_id]} to " f"row={playlist_notes[note_id]}" ) - Notes.update_note(note_id, playlist_notes[note_id]) + Notes.update_note(session, note_id, playlist_notes[note_id]) # Now check tracks # Create dictionaries indexed by row @@ -878,7 +882,8 @@ class Playlist(QTableWidget): set(set(playlist_tracks.keys()) - set(database_tracks.keys())) ): DEBUG(f"_save_playlist(): row {row} missing from database") - PlaylistTracks.add_track(self.db.id, playlist_tracks[row], row) + PlaylistTracks.add_track(session, self.db.id, + playlist_tracks[row], row) # Track rows to remove from database for row in ( @@ -889,7 +894,7 @@ class Playlist(QTableWidget): f"_save_playlist(): row {row} in database not playlist " f"(track={track})" ) - PlaylistTracks.remove_track(self.db.id, row) + PlaylistTracks.remove_track(session, self.db.id, row) # Track rows to update in database for row in ( @@ -902,20 +907,21 @@ class Playlist(QTableWidget): f"to track={playlist_tracks[row]}" ) PlaylistTracks.update_row_track( - self.db.id, row, playlist_tracks[row]) + session, self.db.id, row, playlist_tracks[row]) def _set_column_widths(self): # Column widths from settings - for column in range(self.columnCount()): - # Only show column 0 in test mode - if (column == 0 and not Config.TESTMODE): - self.setColumnWidth(0, 0) - else: - name = f"playlist_col_{str(column)}_width" - record = Settings.get_int(name) - if record.f_int is not None: - self.setColumnWidth(column, record.f_int) + with Session() as session: + for column in range(self.columnCount()): + # Only show column 0 in test mode + if (column == 0 and not Config.TESTMODE): + self.setColumnWidth(0, 0) + else: + name = f"playlist_col_{str(column)}_width" + record = Settings.get_int(session, name) + if record.f_int is not None: + self.setColumnWidth(column, record.f_int) def _set_row_bold(self, row, bold=True): boldfont = QFont() diff --git a/app/songdb.py b/app/songdb.py index 7ef8201..ab23609 100755 --- a/app/songdb.py +++ b/app/songdb.py @@ -7,7 +7,7 @@ import tempfile from config import Config from log import DEBUG, INFO -from model import Tracks, session +from model import Tracks, Session from mutagen.flac import FLAC from pydub import AudioSegment, effects from tinytag import TinyTag @@ -28,15 +28,16 @@ def main(): # Run as required if args.update: INFO("Updating database") - update_db() + with Session() as session: + update_db(session) INFO("Finished") -def add_path_to_db(path): +def add_path_to_db(session, path): "Add passed path to database along with metadata" - track = Tracks.get_or_create(path) + track = Tracks.get_or_create(session, path) tag = TinyTag.get(path) audio = get_audio_segment(path) @@ -143,19 +144,20 @@ def fade_point(audio_segment, fade_threshold=Config.DBFS_FADE, return int(trim_ms) -def rescan_database(): - - tracks = Tracks.get_all_tracks() - total_tracks = len(tracks) - track_count = 0 - for track in tracks: - track_count += 1 - print(f"Track {track_count} of {total_tracks}") - audio = get_audio_segment(track.path) - track.start_gap = leading_silence(audio) - track.fade_at = fade_point(audio) - track.silence_at = trailing_silence(audio) - session.commit() +# Current unused (1 June 2021) +# def rescan_database(session): +# +# tracks = Tracks.get_all_tracks(session) +# total_tracks = len(tracks) +# track_count = 0 +# for track in tracks: +# track_count += 1 +# print(f"Track {track_count} of {total_tracks}") +# audio = get_audio_segment(track.path) +# track.start_gap = leading_silence(audio) +# track.fade_at = fade_point(audio) +# track.silence_at = trailing_silence(audio) +# session.commit() def trailing_silence(audio_segment, silence_threshold=-50.0, @@ -163,14 +165,14 @@ def trailing_silence(audio_segment, silence_threshold=-50.0, return fade_point(audio_segment, silence_threshold, chunk_size) -def update_db(): +def update_db(session): """ Repopulate database """ # Search for tracks in only one of directory and database - db_paths = set(Tracks.get_all_paths()) + db_paths = set(Tracks.get_all_paths(session)) os_paths_list = [] for root, dirs, files in os.walk(Config.ROOT): @@ -188,7 +190,7 @@ def update_db(): for path in list(os_paths - db_paths): # TODO INFO(f"Adding to dataabase: {path}") - add_path_to_db(path) + add_path_to_db(session, path) if __name__ == '__main__' and '__file__' in globals():