diff --git a/app/classes.py b/app/classes.py
index ea86092..e219c9b 100644
--- a/app/classes.py
+++ b/app/classes.py
@@ -46,6 +46,54 @@ def singleton(cls):
return wrapper_singleton
+# DTOs
+@dataclass
+class PlaylistDTO:
+ playlist_id: int
+ name: str
+ open: bool = False
+ favourite: bool = False
+ is_template: bool = False
+
+
+@dataclass
+class QueryDTO:
+ query_id: int
+ name: str
+ favourite: bool
+ filter: Filter
+
+
+@dataclass
+class TrackDTO:
+ track_id: int
+ artist: str
+ bitrate: int
+ duration: int
+ fade_at: int
+ intro: int | None
+ path: str
+ silence_at: int
+ start_gap: int
+ title: str
+ lastplayed: dt.datetime | None
+
+
+@dataclass
+class PlaylistRowDTO(TrackDTO):
+ note: str
+ played: bool
+ playlist_id: int
+ playlistrow_id: int
+ row_number: int
+
+
+@dataclass
+class PlaydatesDTO(TrackDTO):
+ playdate_id: int
+ lastplayed: dt.datetime
+
+
class ApplicationError(Exception):
"""
Custom exception
@@ -124,39 +172,6 @@ class Tags(NamedTuple):
duration: int = 0
-@dataclass
-class PlaylistDTO:
- name: str
- playlist_id: int
- favourite: bool = False
- is_template: bool = False
- open: bool = False
-
-
-@dataclass
-class TrackDTO:
- track_id: int
- artist: str
- bitrate: int
- duration: int
- fade_at: int
- intro: int | None
- path: str
- silence_at: int
- start_gap: int
- title: str
- lastplayed: dt.datetime | None
-
-
-@dataclass
-class PlaylistRowDTO(TrackDTO):
- note: str
- played: bool
- playlist_id: int
- playlistrow_id: int
- row_number: int
-
-
class TrackInfo(NamedTuple):
track_id: int
row_number: int
@@ -177,6 +192,12 @@ class InsertTrack:
note: str
+@dataclass
+class PlayTrack:
+ playlist_id: int
+ track_id: int
+
+
@singleton
@dataclass
class MusicMusterSignals(QObject):
@@ -204,6 +225,7 @@ class MusicMusterSignals(QObject):
# specify that here as it requires us to import PlaylistRow from
# playlistrow.py, which itself imports MusicMusterSignals
signal_set_next_track = pyqtSignal(object)
+ signal_track_started = pyqtSignal(PlayTrack)
span_cells_signal = pyqtSignal(int, int, int, int, int)
status_message_signal = pyqtSignal(str, int)
track_ended_signal = pyqtSignal()
diff --git a/app/config.py b/app/config.py
index 58db885..946204e 100644
--- a/app/config.py
+++ b/app/config.py
@@ -34,6 +34,7 @@ class Config(object):
COLOUR_QUERYLIST_SELECTED = "#d3ffd3"
COLOUR_UNREADABLE = "#dc3545"
COLOUR_WARNING_TIMER = "#ffc107"
+ DB_NOT_FOUND = "Database not found"
DBFS_SILENCE = -50
DEFAULT_COLUMN_WIDTH = 200
DISPLAY_SQL = False
diff --git a/app/dialogs.py b/app/dialogs.py
index 6adb4a1..2907d20 100644
--- a/app/dialogs.py
+++ b/app/dialogs.py
@@ -2,13 +2,7 @@
from typing import Optional
# PyQt imports
-from PyQt6.QtCore import QEvent, Qt
-from PyQt6.QtGui import QKeyEvent
-from PyQt6.QtWidgets import (
- QDialog,
- QListWidgetItem,
- QMainWindow,
-)
+from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
QDialog,
QHBoxLayout,
@@ -16,6 +10,7 @@ from PyQt6.QtWidgets import (
QLineEdit,
QListWidget,
QListWidgetItem,
+ QMainWindow,
QPushButton,
QVBoxLayout,
)
@@ -98,12 +93,10 @@ class TrackInsertDialog(QDialog):
self.setLayout(layout)
self.resize(800, 600)
- # TODO
- # record = Settings.get_setting(self.session, "dbdialog_width")
- # width = record.f_int or 800
- # record = Settings.get_setting(self.session, "dbdialog_height")
- # height = record.f_int or 600
- # self.resize(width, height)
+
+ width = repository.get_setting("dbdialog_width") or 800
+ height = repository.get_setting("dbdialog_height") or 800
+ self.resize(width, height)
self.signals = MusicMusterSignals()
@@ -114,9 +107,9 @@ class TrackInsertDialog(QDialog):
return
if text.startswith("a/") and len(text) > 2:
- self.tracks = repository.tracks_like_artist(text[2:])
+ self.tracks = repository.tracks_by_artist(text[2:])
else:
- self.tracks = repository.tracks_like_title(text)
+ self.tracks = repository.tracks_by_title(text)
for track in self.tracks:
duration_str = ms_to_mmss(track.duration)
diff --git a/app/file_importer.py b/app/file_importer.py
index db23e63..fc71642 100644
--- a/app/file_importer.py
+++ b/app/file_importer.py
@@ -465,7 +465,7 @@ class FileImporter:
# file). Check that because the path field in the database is
# unique and so adding a duplicate will give a db integrity
# error.
- if repository.track_with_path(tfd.destination_path):
+ if repository.track_by_path(tfd.destination_path):
tfd.error = (
"Importing a new track but destination path already exists "
f"in database ({tfd.destination_path})"
diff --git a/app/helpers.py b/app/helpers.py
index 8551c74..66a6232 100644
--- a/app/helpers.py
+++ b/app/helpers.py
@@ -331,32 +331,6 @@ def normalise_track(path: str) -> None:
os.remove(temp_path)
-def remove_substring_case_insensitive(parent_string: str, substring: str) -> str:
- """
- Remove all instances of substring from parent string, case insensitively
- """
-
- # Convert both strings to lowercase for case-insensitive comparison
- lower_parent = parent_string.lower()
- lower_substring = substring.lower()
-
- # Initialize the result string
- result = parent_string
-
- # Continue removing the substring until it's no longer found
- while lower_substring in lower_parent:
- # Find the index of the substring
- index = lower_parent.find(lower_substring)
-
- # Remove the substring
- result = result[:index] + result[index + len(substring) :]
-
- # Update the lowercase versions
- lower_parent = result.lower()
-
- return result
-
-
def send_mail(to_addr: str, from_addr: str, subj: str, body: str) -> None:
# From https://docs.python.org/3/library/email.examples.html
diff --git a/app/musicmuster.py b/app/musicmuster.py
index 0b7fba9..a1a5545 100755
--- a/app/musicmuster.py
+++ b/app/musicmuster.py
@@ -59,7 +59,6 @@ from PyQt6.QtWidgets import (
# Third party imports
# import line_profiler
from pygame import mixer
-from sqlalchemy.orm.session import Session
import stackprinter # type: ignore
# App imports
@@ -67,6 +66,8 @@ from classes import (
ApplicationError,
Filter,
MusicMusterSignals,
+ PlayTrack,
+ QueryDTO,
TrackInfo,
)
from config import Config
@@ -78,6 +79,7 @@ from models import db, Playdates, PlaylistRows, Playlists, Queries, Settings, Tr
from playlistrow import PlaylistRow, TrackSequence
from playlistmodel import PlaylistModel, PlaylistProxyModel
from playlists import PlaylistTab
+import repository
from querylistmodel import QuerylistModel
from ui import icons_rc # noqa F401
from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore
@@ -455,10 +457,9 @@ class ManageQueries(ItemlistManager):
Delete / edit queries
"""
- def __init__(self, session: Session, musicmuster: Window) -> None:
+ def __init__(self, musicmuster: Window) -> None:
super().__init__()
- self.session = session
self.musicmuster = musicmuster
self.refresh_table()
self.exec()
@@ -471,9 +472,11 @@ class ManageQueries(ItemlistManager):
# Build a list of queries
query_list: list[ItemlistItem] = []
- for query in Queries.get_all(self.session):
+ for query in repository.get_all_queries():
query_list.append(
- ItemlistItem(name=query.name, id=query.id, favourite=query.favourite)
+ ItemlistItem(
+ name=query.name, id=query.query_id, favourite=query.favourite
+ )
)
self.populate_table(query_list)
@@ -482,7 +485,7 @@ class ManageQueries(ItemlistManager):
def delete_item(self, query_id: int) -> None:
"""delete query"""
- query = self.session.get(Queries, query_id)
+ query = repository.query_by_id(query_id)
if not query:
raise ApplicationError(
f"manage_template.delete({query_id=}) can't load query"
@@ -491,24 +494,22 @@ class ManageQueries(ItemlistManager):
"Delete query",
f"Delete query '{query.name}': " "Are you sure?",
):
- self.session.delete(query)
- self.session.commit()
+ repository.delete_query(query_id)
self.refresh_table()
- def _edit_item(self, query: Queries) -> None:
+ def _edit_item(self, query: QueryDTO) -> None:
"""Edit query"""
dlg = FilterDialog(query.name, query.filter)
if dlg.exec():
- query.filter = dlg.filter
- query.name = dlg.name_text.text()
- self.session.commit()
+ repository.update_query_filter(query.query_id, dlg.filter)
+ repository.update_query_name(query.query_id, dlg.name_text.text())
def edit_item(self, query_id: int) -> None:
"""Edit query"""
- query = self.session.get(Queries, query_id)
+ query = repository.query_by_id(query_id)
if not query:
raise ApplicationError(
f"manage_template.edit_item({query_id=}) can't load query"
@@ -518,11 +519,7 @@ class ManageQueries(ItemlistManager):
def toggle_favourite(self, query_id: int, favourite: bool) -> None:
"""Mark query as (not) favourite"""
- query = self.session.get(Queries, query_id)
- if not query:
- return
- query.favourite = favourite
- self.session.commit()
+ repository.update_query_favourite(query_id, favourite)
def new_item(self) -> None:
"""Create new query"""
@@ -531,24 +528,21 @@ class ManageQueries(ItemlistManager):
if not query_name:
return
- query = Queries(self.session, query_name, Filter())
+ query = repository.create_query(query_name, Filter())
self._edit_item(query)
self.refresh_table()
def rename_item(self, query_id: int) -> None:
"""rename query"""
- query = self.session.get(Queries, query_id)
+ query = repository.query_by_id(query_id)
if not query:
- raise ApplicationError(
- f"manage_template.delete({query_id=}) can't load query"
- )
+ raise ApplicationError(f"Can't load query ({query_id=})")
new_name = get_name(prompt="New query name", default=query.name)
if not new_name:
return
- query.name = new_name
- self.session.commit()
+ repository.update_query_name(query_id, new_name)
self.change_text(query_id, new_name)
@@ -558,10 +552,9 @@ class ManageTemplates(ItemlistManager):
Delete / edit templates
"""
- def __init__(self, session: Session, musicmuster: Window) -> None:
+ def __init__(self, musicmuster: Window) -> None:
super().__init__()
- self.session = session
self.musicmuster = musicmuster
self.refresh_table()
self.exec()
@@ -574,10 +567,12 @@ class ManageTemplates(ItemlistManager):
# Build a list of templates
template_list: list[ItemlistItem] = []
- for template in Playlists.get_all_templates(self.session):
+ for template in repository.playlists_templates():
template_list.append(
ItemlistItem(
- name=template.name, id=template.id, favourite=template.favourite
+ name=template.name,
+ id=template.playlist_id,
+ favourite=template.favourite,
)
)
@@ -587,7 +582,7 @@ class ManageTemplates(ItemlistManager):
def delete_item(self, template_id: int) -> None:
"""delete template"""
- template = self.session.get(Playlists, template_id)
+ template = repository.playlists_template_by_id(template_id)
if not template:
raise ApplicationError(
f"manage_template.delete({template_id=}) can't load template"
@@ -607,13 +602,12 @@ class ManageTemplates(ItemlistManager):
else:
self.musicmuster.playlist_section.tabPlaylist.removeTab(open_idx)
- self.session.delete(template)
- self.session.commit()
+ repository.delete_playlist(template.playlist_id)
def edit_item(self, template_id: int) -> None:
"""Edit template"""
- template = self.session.get(Playlists, template_id)
+ template = repository.playlists_template_by_id(template_id)
if not template:
raise ApplicationError(
f"manage_template.edit({template_id=}) can't load template"
@@ -625,11 +619,7 @@ class ManageTemplates(ItemlistManager):
def toggle_favourite(self, template_id: int, favourite: bool) -> None:
"""Mark template as (not) favourite"""
- template = self.session.get(Playlists, template_id)
- if not template:
- return
- template.favourite = favourite
- self.session.commit()
+ repository.update_template_favourite(template_id, favourite)
def new_item(
self,
@@ -638,22 +628,18 @@ class ManageTemplates(ItemlistManager):
# Get base template
template_id = self.musicmuster.solicit_template_to_use(
- self.session, template_prompt="New template based upon:"
+ template_prompt="New template based upon:"
)
if template_id is None:
return
# Get new template name
- name = self.musicmuster.get_playlist_name(
- self.session, default="", prompt="New template name:"
- )
+ name = self.musicmuster.get_playlist_name(default="", prompt="New template name:")
if not name:
return
# Create playlist for template and mark is as a template
- template = self.musicmuster._create_playlist(self.session, name, template_id)
- template.is_template = True
- self.session.commit()
+ template = repository.create_playlist(name, template_id, as_template=True)
# Open it for editing
self.musicmuster._open_playlist(template, is_template=True)
@@ -661,19 +647,14 @@ class ManageTemplates(ItemlistManager):
def rename_item(self, template_id: int) -> None:
"""rename template"""
- template = self.session.get(Playlists, template_id)
+ template = repository.playlist_by_id(template_id)
if not template:
raise ApplicationError(
f"manage_template.delete({template_id=}) can't load template"
)
- new_name = self.musicmuster.get_playlist_name(self.session, template.name)
- if not new_name:
- return
-
- template.name = new_name
- self.session.commit()
-
- self.change_text(template_id, new_name)
+ new_name = self.musicmuster.get_playlist_name(template.name)
+ if new_name:
+ repository.playlist_rename(template_id, new_name)
@dataclass
@@ -790,9 +771,8 @@ class PreviewManager:
class QueryDialog(QDialog):
"""Dialog box to handle selecting track from a query"""
- def __init__(self, session: Session, default: int = 0) -> None:
+ def __init__(self, default: int = 0) -> None:
super().__init__()
- self.session = session
self.default = default
# Build a list of (query-name, playlist-id) tuples
@@ -800,8 +780,8 @@ class QueryDialog(QDialog):
self.query_list: list[tuple[str, int]] = []
self.query_list.append((Config.NO_QUERY_NAME, 0))
- for query in Queries.get_all(self.session):
- self.query_list.append((query.name, query.id))
+ for query in repository.get_all_queries():
+ self.query_list.append((query.name, query.query_id))
self.setWindowTitle("Query Selector")
@@ -918,8 +898,7 @@ class QueryDialog(QDialog):
querylist_y=self.y(),
)
for name, value in attributes_to_save.items():
- record = Settings.get_setting(self.session, name)
- record.f_int = value
+ repository.set_setting(name, value)
header = self.table_view.horizontalHeader()
if header is None:
@@ -929,10 +908,9 @@ class QueryDialog(QDialog):
return
for column_number in range(column_count - 1):
attr_name = f"querylist_col_{column_number}_width"
- record = Settings.get_setting(self.session, attr_name)
- record.f_int = self.table_view.columnWidth(column_number)
-
- self.session.commit()
+ repository.set_setting(
+ attr_name, self.table_view.columnWidth(column_number)
+ )
def _column_resize(self, column_number: int, _old: int, _new: int) -> None:
"""
@@ -959,14 +937,14 @@ class QueryDialog(QDialog):
Called when user selects query
"""
- # Get query id
+ # Get query
query_id = self.combo_box.currentData()
- query = self.session.get(Queries, query_id)
+ query = repository.query_by_id(query_id)
if not query:
return
# Create model
- base_model = QuerylistModel(self.session, query.filter)
+ base_model = QuerylistModel(query.filter)
# Create table
self.table_view.setModel(base_model)
@@ -983,10 +961,10 @@ class QueryDialog(QDialog):
def set_window_size(self) -> None:
"""Set window sizes"""
- x = Settings.get_setting(self.session, "querylist_x").f_int or 100
- y = Settings.get_setting(self.session, "querylist_y").f_int or 100
- width = Settings.get_setting(self.session, "querylist_width").f_int or 100
- height = Settings.get_setting(self.session, "querylist_height").f_int or 100
+ x = repository.get_setting("querylist_x") or 100
+ y = repository.get_setting("querylist_y") or 100
+ width = repository.get_setting("querylist_width") or 100
+ height = repository.get_setting("querylist_height") or 100
self.setGeometry(x, y, width, height)
def set_column_sizes(self) -> None:
@@ -1002,17 +980,12 @@ class QueryDialog(QDialog):
# Last column is set to stretch so ignore it here
for column_number in range(column_count - 1):
attr_name = f"querylist_col_{column_number}_width"
- record = Settings.get_setting(self.session, attr_name)
- if record.f_int is not None:
- self.table_view.setColumnWidth(column_number, record.f_int)
- else:
- self.table_view.setColumnWidth(
- column_number, Config.DEFAULT_COLUMN_WIDTH
- )
+ width = repository.get_setting(attr_name) or Config.DEFAULT_COLUMN_WIDTH
+ self.table_view.setColumnWidth(column_number, width)
class SelectPlaylistDialog(QDialog):
- def __init__(self, parent=None, playlists=None, session=None):
+ def __init__(self, parent=None, playlists=None):
super().__init__()
if playlists is None:
@@ -1022,13 +995,10 @@ class SelectPlaylistDialog(QDialog):
self.ui.lstPlaylists.itemDoubleClicked.connect(self.list_doubleclick)
self.ui.buttonBox.accepted.connect(self.open)
self.ui.buttonBox.rejected.connect(self.close)
- self.session = session
self.playlist = None
- record = Settings.get_setting(self.session, "select_playlist_dialog_width")
- width = record.f_int or 800
- record = Settings.get_setting(self.session, "select_playlist_dialog_height")
- height = record.f_int or 600
+ width = repository.get_setting("select_playlist_dialog_width") or 800
+ height = repository.get_setting("select_playlist_dialog_height") or 800
self.resize(width, height)
for playlist in playlists:
@@ -1038,13 +1008,8 @@ class SelectPlaylistDialog(QDialog):
self.ui.lstPlaylists.addItem(p)
def __del__(self): # review
- record = Settings.get_setting(self.session, "select_playlist_dialog_height")
- record.f_int = self.height()
-
- record = Settings.get_setting(self.session, "select_playlist_dialog_width")
- record.f_int = self.width()
-
- self.session.commit()
+ repository.set_setting("select_playlist_dialog_height", self.height())
+ repository.set_setting("select_playlist_dialog_width", self.width())
def list_doubleclick(self, entry): # review
self.playlist = entry.data(Qt.ItemDataRole.UserRole)
@@ -1224,34 +1189,26 @@ class Window(QMainWindow):
self, "Track playing", "Can't close application while track is playing"
)
else:
- with db.Session() as session:
- # Save tab number of open playlists
- open_playlist_ids: dict[int, int] = {}
- for idx in range(self.playlist_section.tabPlaylist.count()):
- open_playlist_ids[
- self.playlist_section.tabPlaylist.widget(idx).playlist_id
- ] = idx
- Playlists.clear_tabs(session, list(open_playlist_ids.keys()))
- for playlist_id, idx in open_playlist_ids.items():
- playlist = session.get(Playlists, playlist_id)
- if playlist:
- playlist.tab = idx
+ # Save tab number of open playlists
+ playlist_id_to_tab: dict[int, int] = {}
+ for idx in range(self.playlist_section.tabPlaylist.count()):
+ playlist_id_to_tab[
+ self.playlist_section.tabPlaylist.widget(idx).playlist_id
+ ] = idx
+ repository.playlist_save_tabs(playlist_id_to_tab)
- # Save window attributes
- attributes_to_save = dict(
- mainwindow_height=self.height(),
- mainwindow_width=self.width(),
- mainwindow_x=self.x(),
- mainwindow_y=self.y(),
- active_tab=self.playlist_section.tabPlaylist.currentIndex(),
- )
- for name, value in attributes_to_save.items():
- record = Settings.get_setting(session, name)
- record.f_int = value
+ # Save window attributes
+ attributes_to_save = dict(
+ mainwindow_height=self.height(),
+ mainwindow_width=self.width(),
+ mainwindow_x=self.x(),
+ mainwindow_y=self.y(),
+ active_tab=self.playlist_section.tabPlaylist.currentIndex(),
+ )
+ for name, value in attributes_to_save.items():
+ repository.set_setting(name, value)
- session.commit()
-
- event.accept()
+ event.accept()
# # # # # # # # # # Internal utility functions # # # # # # # # # #
@@ -1347,87 +1304,78 @@ class Window(QMainWindow):
def get_new_playlist_dynamic_submenu_items(
self,
- ) -> list[dict[str, str | tuple[Session, int] | bool]]:
+ ) -> list[dict[str, str | int | bool]]:
"""
Return dynamically generated menu items, in this case
templates marked as favourite from which to generate a
new playlist.
- The handler is to call create_playlist with a session
- and template_id.
+ The handler is to call create_playlist_from_template with a dict
+ of arguments.
"""
- with db.Session() as session:
- submenu_items: list[dict[str, str | tuple[Session, int] | bool]] = [
+ submenu_items: list[dict[str, str | int | bool]] = [
+ {
+ "text": "Show all",
+ "handler": "create_playlist_from_template",
+ "args": (0),
+ },
+ {
+ "separator": True,
+ },
+ ]
+ templates = repository.playlists_templates()
+ for template in templates:
+ submenu_items.append(
{
- "text": "Show all",
+ "text": template.name,
"handler": "create_playlist_from_template",
- "args": (session, 0),
- },
- {
- "separator": True,
- },
- ]
- templates = Playlists.get_favourite_templates(session)
- for template in templates:
- submenu_items.append(
- {
- "text": template.name,
- "handler": "create_playlist_from_template",
- "args": (
- session,
- template.id,
- ),
- }
- )
+ "args": template.playlist_id,
+ }
+ )
- return submenu_items
+ return submenu_items
def get_query_dynamic_submenu_items(
self,
- ) -> list[dict[str, str | tuple[Session, int] | bool]]:
+ ) -> list[dict[str, str | int | bool]]:
"""
Return dynamically generated menu items, in this case
templates marked as favourite from which to generate a
new playlist.
- The handler is to call show_query with a session
- and query_id.
+ The handler is to call show_query with a query_id.
"""
- with db.Session() as session:
- submenu_items: list[dict[str, str | tuple[Session, int] | bool]] = [
+ submenu_items: list[dict[str, str | int | bool]] = [
+ {
+ "text": "Show all",
+ "handler": "show_query",
+ "args": 0,
+ },
+ {
+ "separator": True,
+ },
+ ]
+ queries = repository.get_all_queries(favourites_only=True)
+ for query in queries:
+ submenu_items.append(
{
- "text": "Show all",
+ "text": query.name,
"handler": "show_query",
- "args": (session, 0),
- },
- {
- "separator": True,
- },
- ]
- queries = Queries.get_favourites(session)
- for query in queries:
- submenu_items.append(
- {
- "text": query.name,
- "handler": "show_query",
- "args": (
- session,
- query.id,
- ),
- }
- )
+ "args": query.query_id,
+ }
+ )
- return submenu_items
+ return submenu_items
- def show_query(self, session: Session, query_id: int) -> None:
+ def show_query(self, query_id: int) -> None:
"""
Show query dialog with query_id selected
"""
# Keep a reference else it will be gc'd
- self.query_dialog = QueryDialog(session, query_id)
+ self.query_dialog = QueryDialog(query_id)
if self.query_dialog.exec():
new_row_number = self.current_row_or_end()
base_model = self.current.base_model
@@ -1444,7 +1392,9 @@ class Window(QMainWindow):
move_existing = True
if move_existing and existing_prd:
- base_model.move_track_add_note(new_row_number, existing_prd, note="")
+ base_model.move_track_add_note(
+ new_row_number, existing_prd, note=""
+ )
else:
base_model.insert_row(new_row_number, track_id)
@@ -1453,15 +1403,13 @@ class Window(QMainWindow):
# # # # # # # # # # Playlist management functions # # # # # # # # # #
@log_call
- def _create_playlist(
- self, session: Session, name: str, template_id: int
- ) -> Playlists:
+ def _create_playlist(self, name: str, template_id: int) -> Playlists:
"""
Create a playlist in the database, populate it from the template
if template_id > 0, and return the Playlists object.
"""
- return Playlists(session, name, template_id)
+ return repository.create_playlist(name, template_id)
@log_call
def _open_playlist(self, playlist: Playlists, is_template: bool = False) -> int:
@@ -1475,7 +1423,7 @@ class Window(QMainWindow):
"""
# Create base model and proxy model
- base_model = PlaylistModel(playlist.id, is_template)
+ base_model = PlaylistModel(playlist.playlist_id, is_template)
proxy_model = PlaylistProxyModel()
proxy_model.setSourceModel(base_model)
@@ -1484,7 +1432,7 @@ class Window(QMainWindow):
idx = self.playlist_section.tabPlaylist.addTab(playlist_tab, playlist.name)
# Mark playlist as open
- playlist.mark_open()
+ repository.playlist_mark_status(playlist.playlist_id, open=True)
# Switch to new tab
self.playlist_section.tabPlaylist.setCurrentIndex(idx)
@@ -1493,93 +1441,87 @@ class Window(QMainWindow):
return idx
@log_call
- def create_playlist_from_template(self, session: Session, template_id: int) -> None:
+ def create_playlist_from_template(self, template_id: int) -> None:
"""
Prompt for new playlist name and create from passed template_id
"""
if template_id == 0:
# Show all templates
- selected_template_id = self.solicit_template_to_use(session)
+ selected_template_id = self.solicit_template_to_use()
if selected_template_id is None:
return
else:
template_id = selected_template_id
- playlist_name = self.get_playlist_name(session)
+ playlist_name = self.get_playlist_name()
if not playlist_name:
return
- playlist = self._create_playlist(session, playlist_name, template_id)
- self._open_playlist(playlist)
- session.commit()
+ _ = repository.create_playlist(playlist_name, template_id)
@log_call
def delete_playlist(self, checked: bool = False) -> None:
"""
- Delete current playlist
+ Delete current playlist. checked paramater passed by menu system
+ but unused.
"""
- with db.Session() as session:
- playlist_id = self.current.playlist_id
- playlist = session.get(Playlists, playlist_id)
- if playlist:
- if helpers.ask_yes_no(
- "Delete playlist",
- f"Delete playlist '{playlist.name}': " "Are you sure?",
- ):
- if self.close_playlist_tab():
- session.delete(playlist)
- session.commit()
- else:
- log.error("Failed to retrieve playlist")
+ playlist = repository.playlist_by_id(self.current.playlist_id)
+ if playlist:
+ if helpers.ask_yes_no(
+ "Delete playlist",
+ f"Delete playlist '{playlist.name}': " "Are you sure?",
+ ):
+ if self.close_playlist_tab():
+ repository.delete_playlist(self.current.playlist_id)
+ else:
+ log.error("Failed to retrieve playlist")
def open_existing_playlist(self, checked: bool = False) -> None:
"""Open existing playlist"""
- with db.Session() as session:
- playlists = Playlists.get_closed(session)
- dlg = SelectPlaylistDialog(self, playlists=playlists, session=session)
- dlg.exec()
- playlist = dlg.playlist
- if playlist:
- self._open_playlist(playlist)
- session.commit()
+ playlists = repository.playlists_closed()
+ dlg = SelectPlaylistDialog(self, playlists=playlists)
+ dlg.exec()
+ playlist = dlg.playlist
+ if playlist:
+ self._open_playlist(playlist)
def save_as_template(self, checked: bool = False) -> None:
"""Save current playlist as template"""
- with db.Session() as session:
- template_names = [a.name for a in Playlists.get_all_templates(session)]
+ template_names = [a.name for a in repository.playlists_templates()]
- while True:
- # Get name for new template
- dlg = QInputDialog(self)
- dlg.setInputMode(QInputDialog.InputMode.TextInput)
- dlg.setLabelText("Template name:")
- dlg.resize(500, 100)
- ok = dlg.exec()
- if not ok:
- return
+ while True:
+ # Get name for new template
+ dlg = QInputDialog(self)
+ dlg.setInputMode(QInputDialog.InputMode.TextInput)
+ dlg.setLabelText("Template name:")
+ dlg.resize(500, 100)
+ ok = dlg.exec()
+ if not ok:
+ return
- template_name = dlg.textValue()
- if template_name not in template_names:
- break
- helpers.show_warning(
- self, "Duplicate template", "Template name already in use"
- )
- Playlists.save_as_template(session, self.current.playlist_id, template_name)
- session.commit()
- helpers.show_OK("Template", "Template saved", self)
+ template_name = dlg.textValue()
+ if template_name not in template_names:
+ break
+ helpers.show_warning(
+ self, "Duplicate template", "Template name already in use"
+ )
+ repository.save_as_template(self.current.playlist_id, template_name)
+ helpers.show_OK("Template", "Template saved", self)
def get_playlist_name(
- self, session: Session, default: str = "", prompt: str = "Playlist name:"
+ self, default: str = "", prompt: str = "Playlist name:"
) -> Optional[str]:
"""Get a name from the user"""
dlg = QInputDialog(self)
dlg.setInputMode(QInputDialog.InputMode.TextInput)
dlg.setLabelText(prompt)
+ all_playlist_names = [a.name for a in repository.get_all_playlists()]
+
while True:
if default:
dlg.setTextValue(default)
@@ -1587,7 +1529,7 @@ class Window(QMainWindow):
ok = dlg.exec()
if ok:
proposed_name = dlg.textValue()
- if Playlists.name_is_available(session, proposed_name):
+ if proposed_name not in all_playlist_names:
return proposed_name
else:
helpers.show_warning(
@@ -1600,7 +1542,7 @@ class Window(QMainWindow):
return None
def solicit_template_to_use(
- self, session: Session, template_prompt: Optional[str] = None
+ self, template_prompt: Optional[str] = None
) -> Optional[int]:
"""
Have user select a template. Return the template.id, or None if they cancel.
@@ -1610,15 +1552,14 @@ class Window(QMainWindow):
template_name_id_list: list[tuple[str, int]] = []
template_name_id_list.append((Config.NO_TEMPLATE_NAME, 0))
- with db.Session() as session:
- for template in Playlists.get_all_templates(session):
- template_name_id_list.append((template.name, template.id))
+ for template in repository.playlists_templates():
+ template_name_id_list.append((template.name, template.playlist_id))
- dlg = TemplateSelectorDialog(template_name_id_list, template_prompt)
- if not dlg.exec() or dlg.selected_id is None:
- return None # User cancelled
+ dlg = TemplateSelectorDialog(template_name_id_list, template_prompt)
+ if not dlg.exec() or dlg.selected_id is None:
+ return None # User cancelled
- return dlg.selected_id
+ return dlg.selected_id
# # # # # # # # # # Manage templates and queries # # # # # # # # # #
@@ -1627,16 +1568,14 @@ class Window(QMainWindow):
Simply instantiate the manage_queries class
"""
- with db.Session() as session:
- _ = ManageQueries(session, self)
+ _ = ManageQueries(self)
def manage_templates_wrapper(self, checked: bool = False) -> None:
"""
- Simply instantiate the manage_queries class
+ Simply instantiate the manage_templates class
"""
- with db.Session() as session:
- _ = ManageTemplates(session, self)
+ _ = ManageTemplates(self)
# # # # # # # # # # Miscellaneous functions # # # # # # # # # #
@@ -1655,9 +1594,7 @@ class Window(QMainWindow):
except subprocess.CalledProcessError as exc_info:
git_tag = str(exc_info.output)
- with db.Session() as session:
- if session.bind:
- dbname = session.bind.engine.url.database
+ dbname = repository.get_db_name()
QMessageBox.information(
self,
@@ -1723,11 +1660,8 @@ class Window(QMainWindow):
)
return False
- # Record playlist as closed and update remaining playlist tabs
- with db.Session() as session:
- playlist = session.get(Playlists, closing_tab_playlist_id)
- if playlist:
- playlist.close(session)
+ # Record playlist as closed
+ repository.playlist_mark_status(open=False)
# Close playlist and remove tab
self.playlist_section.tabPlaylist.widget(tab_index).close()
@@ -1810,15 +1744,16 @@ class Window(QMainWindow):
path += ".csv"
with open(path, "w") as f:
- with db.Session() as session:
- for playdate in Playdates.played_after(session, start_dt):
- f.write(f"{playdate.track.artist},{playdate.track.title}\n")
+ for playdate in repository.playdates_between_dates(start_dt):
+ f.write(f"{playdate.artist},{playdate.title}\n")
def drop3db(self) -> None:
"""Drop music level by 3db if button checked"""
if self.track_sequence.current:
- self.track_sequence.current.drop3db(self.footer_section.btnDrop3db.isChecked())
+ self.track_sequence.current.drop3db(
+ self.footer_section.btnDrop3db.isChecked()
+ )
@log_call
def enable_escape(self, enabled: bool) -> None:
@@ -1872,41 +1807,37 @@ class Window(QMainWindow):
playlist_id = self.current.playlist_id
- with db.Session() as session:
- # Get output filename
- playlist = session.get(Playlists, playlist_id)
- if not playlist:
- return
+ playlist = repository.playlist_by_id(playlist_id)
+ if not playlist:
+ return
- pathspec = QFileDialog.getSaveFileName(
- self,
- "Save Playlist",
- directory=f"{playlist.name}.m3u",
- filter="M3U files (*.m3u);;All files (*.*)",
- )
- if not pathspec:
- return
- path = pathspec[0]
- if not path.endswith(".m3u"):
- path += ".m3u"
+ # Get output filename
+ pathspec = QFileDialog.getSaveFileName(
+ self,
+ "Save Playlist",
+ directory=f"{playlist.name}.m3u",
+ filter="M3U files (*.m3u);;All files (*.*)",
+ )
+ if not pathspec:
+ return
+ path = pathspec[0]
+ if not path.endswith(".m3u"):
+ path += ".m3u"
- # Get list of track rows for this playlist
- plrs = PlaylistRows.get_rows_with_tracks(session, playlist_id)
- with open(path, "w") as f:
- # Required directive on first line
- f.write("#EXTM3U\n")
- for track in [a.track for a in plrs]:
- if track.duration is None:
- track.duration = 0
- f.write(
- "#EXTINF:"
- f"{int(track.duration / 1000)},"
- f"{track.title} - "
- f"{track.artist}"
- "\n"
- f"{track.path}"
- "\n"
- )
+ # Get list of track rows for this playlist
+ with open(path, "w") as f:
+ # Required directive on first line
+ f.write("#EXTM3U\n")
+ for playlistrow in repository.get_playlist_rows(playlist_id):
+ f.write(
+ "#EXTINF:"
+ f"{int(playlistrow.duration / 1000)},"
+ f"{playlistrow.title} - "
+ f"{playlistrow.artist}"
+ "\n"
+ f"{playlistrow.path}"
+ "\n"
+ )
def fade(self, checked: bool = False) -> None:
"""Fade currently playing track"""
@@ -1972,34 +1903,23 @@ class Window(QMainWindow):
def insert_track(self, checked: bool = False) -> None:
"""Show dialog box to select and add track from database"""
- dlg = TrackInsertDialog(
- parent=self,
- playlist_id=self.active_tab().playlist_id
- )
+ dlg = TrackInsertDialog(parent=self, playlist_id=self.active_tab().playlist_id)
dlg.exec()
@log_call
def load_last_playlists(self) -> None:
- """Load the playlists that were open when the last session closed"""
+ """Load the playlists that were open when app was last closed"""
playlist_ids = []
- with db.Session() as session:
- for playlist in Playlists.get_open(session):
- if playlist:
- # Create tab
- playlist_ids.append(self._open_playlist(playlist))
+ for playlist in repository.playlists_open():
+ if playlist:
+ # Create tab
+ playlist_ids.append(self._open_playlist(playlist))
- # Set active tab
- record = Settings.get_setting(session, "active_tab")
- if record.f_int is not None and record.f_int >= 0:
- self.playlist_section.tabPlaylist.setCurrentIndex(record.f_int)
-
- # Tabs may move during use. Rather than track where tabs
- # are, we record the tab index when we close the main
- # window. To avoid possible duplicate tab entries, we null
- # them all out now.
- Playlists.clear_tabs(session, playlist_ids)
- session.commit()
+ # Set active tab
+ value = repository.get_setting("active_tab")
+ if value is not None and value >= 0:
+ self.playlist_section.tabPlaylist.setCurrentIndex(value)
def lookup_row_in_songfacts(self, checked: bool = False) -> None:
"""
@@ -2047,25 +1967,21 @@ class Window(QMainWindow):
playlists = []
source_playlist_id = self.current.playlist_id
- with db.Session() as session:
- for playlist in Playlists.get_all(session):
- if playlist.id == source_playlist_id:
- continue
- else:
- playlists.append(playlist)
-
- dlg = SelectPlaylistDialog(self, playlists=playlists, session=session)
- dlg.exec()
- if not dlg.playlist:
- return
- to_playlist_id = dlg.playlist.id
-
- # Get row number in destination playlist
- last_row = PlaylistRows.get_last_used_row(session, to_playlist_id)
- if last_row is not None:
- to_row = last_row + 1
+ for playlist in repository.get_all_playlists():
+ if playlist.id == source_playlist_id:
+ continue
else:
- to_row = 0
+ playlists.append(playlist)
+
+ dlg = SelectPlaylistDialog(self, playlists=playlists)
+ dlg.exec()
+ if not dlg.playlist:
+ return
+ to_playlist_id = dlg.playlist.id
+
+ # Add to end of target playlist, so target row will be length of
+ # playlist
+ to_row = repository.playlist_row_count(to_playlist_id)
# Move rows
self.current.base_model.move_rows_between_playlists(
@@ -2153,7 +2069,9 @@ class Window(QMainWindow):
to_playlist_model.set_next_row(set_next_row)
@log_call
- def play_next(self, position: Optional[float] = None, checked: bool = False) -> None:
+ def play_next(
+ self, position: Optional[float] = None, checked: bool = False
+ ) -> None:
"""
Play next track, optionally from passed position.
@@ -2223,20 +2141,29 @@ class Window(QMainWindow):
self.catch_return_key = True
self.show_status_message("Play controls: Disabled", 0)
- # Notify playlist
- self.active_tab().current_track_started()
+ # Notify others
+ self.signals.signal_track_started.emit(
+ PlayTrack(self.track_sequence.current.playlist_id,
+ self.track_sequence.current.track_id)
+ )
+
+ # TODO: ensure signal_track_started does all this:
+ # self.active_tab().current_track_started()
+ # Update playdates
+ # Set toolips for hdrPreviousTrack (but let's do that dynamically
+ # on hover in future)
+ # with s-e-s-s-i-o-n:
+ # last_played = Playdates.last_played_tracks(s-e-s-s-i-o-n)
+ # tracklist = []
+ # for lp in last_played:
+ # track = s-e-s-s-i-o-n.get(Tracks, lp.track_id)
+ # tracklist.append(f"{track.title} ({track.artist})")
+ # tt = "
".join(tracklist)
+
+ # self.header_section.hdrPreviousTrack.setToolTip(tt)
# Update headers
self.update_headers()
- with db.Session() as session:
- last_played = Playdates.last_played_tracks(session)
- tracklist = []
- for lp in last_played:
- track = session.get(Tracks, lp.track_id)
- tracklist.append(f"{track.title} ({track.artist})")
- tt = "
".join(tracklist)
-
- self.header_section.hdrPreviousTrack.setToolTip(tt)
def preview(self) -> None:
"""
@@ -2253,29 +2180,27 @@ class Window(QMainWindow):
if self.track_sequence.next:
if self.track_sequence.next.track_id:
track_info = TrackInfo(
- self.track_sequence.next.track_id, self.track_sequence.next.row_number
+ self.track_sequence.next.track_id,
+ self.track_sequence.next.row_number,
)
else:
return
if not track_info:
return
self.preview_manager.row_number = track_info.row_number
- with db.Session() as session:
- track = session.get(Tracks, track_info.track_id)
- if not track:
- raise ApplicationError(
- f"musicmuster.preview: unable to retreive track {track_info.track_id=}"
- )
- self.preview_manager.set_track_info(
- track_id=track.id,
- track_path=track.path,
- track_intro=track.intro
- )
- self.preview_manager.play()
- self.show_status_message(
- f"Preview: {track.title} / {track.artist} (row {track_info.row_number})",
- 0
+ track = repository.track_by_id(track_info.track_id)
+ if not track:
+ raise ApplicationError(
+ f"musicmuster.preview: unable to retreive track {track_info.track_id=}"
)
+ self.preview_manager.set_track_info(
+ track_id=track.track_id, track_path=track.path, track_intro=track.intro or 0
+ )
+ self.preview_manager.play()
+ self.show_status_message(
+ f"Preview: {track.title} / {track.artist} (row {track_info.row_number})",
+ 0,
+ )
else:
self.preview_manager.stop()
self.show_status_message("", 0)
@@ -2311,21 +2236,15 @@ class Window(QMainWindow):
row_number = self.preview_manager.row_number
if not row_number:
return
- with db.Session() as session:
- track = session.get(Tracks, track_id)
- if track:
- # Save intro as millisends rounded to nearest 0.1
- # second because editor spinbox only resolves to 0.1
- # seconds
- intro = round(self.preview_manager.get_playtime() / 100) * 100
- track.intro = intro
- session.commit()
- self.preview_manager.set_intro(intro)
- self.current.base_model.refresh_row(row_number)
- roles = [
- Qt.ItemDataRole.DisplayRole,
- ]
- self.current.base_model.invalidate_row(row_number, roles)
+
+ intro = round(self.preview_manager.get_playtime() / 100) * 100
+ repository.set_track_intro(track_id, intro)
+ self.preview_manager.set_intro(intro)
+ self.current.base_model.refresh_row(row_number)
+ roles = [
+ Qt.ItemDataRole.DisplayRole,
+ ]
+ self.current.base_model.invalidate_row(row_number, roles)
def preview_start(self) -> None:
"""Restart preview"""
@@ -2350,19 +2269,16 @@ class Window(QMainWindow):
def rename_playlist(self, checked: bool = False) -> None:
"""
- Rename current playlist
+ Rename current playlist. checked is passed by menu but not used here
"""
- with db.Session() as session:
- playlist_id = self.current.playlist_id
- playlist = session.get(Playlists, playlist_id)
- if playlist:
- new_name = self.get_playlist_name(session, playlist.name)
- if new_name:
- playlist.rename(session, new_name)
- idx = self.tabBar.currentIndex()
- self.tabBar.setTabText(idx, new_name)
- session.commit()
+ playlist = repository.playlist_by_id(self.current.playlist_id)
+ if playlist:
+ new_name = self.get_playlist_name(playlist.name)
+ if new_name:
+ repository.playlist_rename(playlist.id, new_name)
+ idx = self.tabBar.currentIndex()
+ self.tabBar.setTabText(idx, new_name)
def return_pressed_in_error(self) -> bool:
"""
@@ -2386,7 +2302,9 @@ class Window(QMainWindow):
# default to NOT playing the next track, else default to
# playing it.
default_yes: bool = self.track_sequence.current.start_time is not None and (
- (dt.datetime.now() - self.track_sequence.current.start_time).total_seconds()
+ (
+ dt.datetime.now() - self.track_sequence.current.start_time
+ ).total_seconds()
* 1000
> Config.PLAY_NEXT_GUARD_MS
)
@@ -2441,9 +2359,12 @@ class Window(QMainWindow):
and self.track_sequence.current.resume_marker
):
elapsed_ms = (
- self.track_sequence.current.duration * self.track_sequence.current.resume_marker
+ self.track_sequence.current.duration
+ * self.track_sequence.current.resume_marker
+ )
+ self.track_sequence.current.start_time -= dt.timedelta(
+ milliseconds=elapsed_ms
)
- self.track_sequence.current.start_time -= dt.timedelta(milliseconds=elapsed_ms)
def search_playlist(self, checked: bool = False) -> None:
"""Show text box to search playlist"""
@@ -2496,12 +2417,11 @@ class Window(QMainWindow):
def set_main_window_size(self) -> None:
"""Set size of window from database"""
- with db.Session() as session:
- x = Settings.get_setting(session, "mainwindow_x").f_int or 100
- y = Settings.get_setting(session, "mainwindow_y").f_int or 100
- width = Settings.get_setting(session, "mainwindow_width").f_int or 100
- height = Settings.get_setting(session, "mainwindow_height").f_int or 100
- self.setGeometry(x, y, width, height)
+ x = repository.get_setting("mainwindow_x") or 100
+ y = repository.get_setting("mainwindow_y") or 100
+ width = repository.get_setting("mainwindow_width") or 100
+ height = repository.get_setting("mainwindow_height") or 100
+ self.setGeometry(x, y, width, height)
@log_call
def set_selected_track_next(self, checked: bool = False) -> None:
@@ -2767,7 +2687,8 @@ class Window(QMainWindow):
if (
self.track_sequence.current
and self.track_sequence.next
- and self.track_sequence.current.playlist_id == self.track_sequence.next.playlist_id
+ and self.track_sequence.current.playlist_id
+ == self.track_sequence.next.playlist_id
):
set_next = False
@@ -2831,12 +2752,10 @@ if __name__ == "__main__":
# Run as required
if args.check_db:
log.debug("Checking database")
- with db.Session() as session:
- check_db(session)
+ check_db()
elif args.update_bitrates:
log.debug("Update bitrates")
- with db.Session() as session:
- update_bitrates(session)
+ update_bitrates()
else:
app = QApplication(sys.argv)
try:
diff --git a/app/querylistmodel.py b/app/querylistmodel.py
index 90567ad..974ca15 100644
--- a/app/querylistmodel.py
+++ b/app/querylistmodel.py
@@ -21,7 +21,6 @@ from PyQt6.QtGui import (
)
# Third party imports
-from sqlalchemy.orm.session import Session
# import snoop # type: ignore
@@ -39,8 +38,9 @@ from helpers import (
show_warning,
)
from log import log
-from models import db, Playdates, Tracks
+from models import db, Playdates
from playlistrow import PlaylistRow
+import repository
@dataclass
@@ -64,7 +64,7 @@ class QuerylistModel(QAbstractTableModel):
"""
- def __init__(self, session: Session, filter: Filter) -> None:
+ def __init__(self, filter: Filter) -> None:
"""
Load query
"""
@@ -72,7 +72,6 @@ class QuerylistModel(QAbstractTableModel):
log.debug(f"QuerylistModel.__init__({filter=})")
super().__init__()
- self.session = session
self.filter = filter
self.querylist_rows: dict[int, QueryRow] = {}
@@ -230,7 +229,7 @@ class QuerylistModel(QAbstractTableModel):
row = 0
try:
- results = Tracks.get_filtered_tracks(self.session, self.filter)
+ results = repository.get_filtered_tracks(self.filter)
for result in results:
lastplayed = None
if hasattr(result, "playdates"):
@@ -244,7 +243,7 @@ class QuerylistModel(QAbstractTableModel):
lastplayed=lastplayed,
path=result.path,
title=result.title,
- track_id=result.id,
+ track_id=result.track_id,
)
self.querylist_rows[row] = queryrow
@@ -275,16 +274,7 @@ class QuerylistModel(QAbstractTableModel):
if column != QueryCol.LAST_PLAYED.value:
return QVariant()
- with db.Session() as session:
- track_id = self.querylist_rows[row].track_id
- if not track_id:
- return QVariant()
- playdates = Playdates.last_playdates(session, track_id)
- return (
- "
".join(
- [
- a.lastplayed.strftime(Config.LAST_PLAYED_TOOLTIP_DATE_FORMAT)
- for a in reversed(playdates)
- ]
- )
- )
+ track_id = self.querylist_rows[row].track_id
+ if not track_id:
+ return QVariant()
+ return repository.get_last_played_dates(track_id)
diff --git a/app/repository.py b/app/repository.py
index 6a378bd..0c9839e 100644
--- a/app/repository.py
+++ b/app/repository.py
@@ -1,4 +1,5 @@
# Standard library imports
+import datetime as dt
import re
# PyQt imports
@@ -13,10 +14,17 @@ from sqlalchemy import (
from sqlalchemy.orm import aliased
from sqlalchemy.orm.session import Session
from sqlalchemy.sql.elements import BinaryExpression, ColumnElement
-from classes import ApplicationError, PlaylistRowDTO
# App imports
-from classes import PlaylistDTO, TrackDTO
+from classes import (
+ ApplicationError,
+ Filter,
+ PlaydatesDTO,
+ PlaylistDTO,
+ PlaylistRowDTO,
+ QueryDTO,
+ TrackDTO,
+)
from config import Config
from log import log, log_call
from models import (
@@ -25,13 +33,14 @@ from models import (
Playdates,
PlaylistRows,
Playlists,
+ Queries,
Settings,
Tracks,
)
# Helper functions
-
+@log_call
def _remove_substring_case_insensitive(parent_string: str, substring: str) -> str:
"""
Remove all instances of substring from parent string, case insensitively
@@ -107,6 +116,7 @@ def get_colour(text: str, foreground: bool = False) -> str:
return rec.colour
+@log_call
def remove_colour_substring(text: str) -> str:
"""
Remove text that identifies the colour to be used if strip_substring is True
@@ -118,6 +128,131 @@ def remove_colour_substring(text: str) -> str:
# Track functions
+@log_call
+def _tracks_where(
+ query: BinaryExpression | ColumnElement[bool],
+ filter_by_last_played: bool = False,
+ last_played_before: dt.datetime | None = None,
+) -> list[TrackDTO]:
+ """
+ Return tracks selected by query
+ """
+
+ # Alibas PlaydatesTable for subquery
+ LatestPlaydate = aliased(Playdates)
+
+ # Create a 'latest playdate' subquery
+ latest_playdate_subq = (
+ select(
+ LatestPlaydate.track_id,
+ func.max(LatestPlaydate.lastplayed).label("lastplayed"),
+ )
+ .group_by(LatestPlaydate.track_id)
+ .subquery()
+ )
+ if not filter_by_last_played:
+ query = query.outerjoin(
+ latest_playdate_subq, Tracks.id == latest_playdate_subq.c.track_id
+ )
+ else:
+ # We are filtering by last played. If last_played_before is None,
+ # we want tracks that have never been played
+ if last_played_before is None:
+ query = query.outerjoin(Playdates, Tracks.id == Playdates.track_id).where(
+ Playdates.id.is_(None)
+ )
+ else:
+ query = query.join(
+ latest_playdate_subq, Tracks.id == latest_playdate_subq.c.track_id
+ ).where(latest_playdate_subq.c.max_last_played < last_played_before)
+ pass
+
+ stmt = select(
+ Tracks.id.label("track_id"),
+ Tracks.artist,
+ Tracks.bitrate,
+ Tracks.duration,
+ Tracks.fade_at,
+ Tracks.intro,
+ Tracks.path,
+ Tracks.silence_at,
+ Tracks.start_gap,
+ Tracks.title,
+ latest_playdate_subq.c.lastplayed,
+ ).where(query)
+
+ results: list[TrackDTO] = []
+
+ with db.Session() as session:
+ records = session.execute(stmt).all()
+ for record in records:
+ dto = TrackDTO(
+ artist=record.artist,
+ bitrate=record.bitrate,
+ duration=record.duration,
+ fade_at=record.fade_at,
+ intro=record.intro,
+ lastplayed=record.lastplayed,
+ path=record.path,
+ silence_at=record.silence_at,
+ start_gap=record.start_gap,
+ title=record.title,
+ track_id=record.track_id,
+ )
+ results.append(dto)
+
+ return results
+
+
+def track_by_path(path: str) -> TrackDTO | None:
+ """
+ Return track with passed path or None
+ """
+
+ track_list = _tracks_where(Tracks.path.ilike(path))
+ if not track_list:
+ return None
+ if len(track_list) > 1:
+ raise ApplicationError(f"Duplicate {path=}")
+ return track_list[0]
+
+
+def track_by_id(track_id: int) -> TrackDTO | None:
+ """
+ Return track with specified id
+ """
+
+ track_list = _tracks_where(Tracks.id == track_id)
+ if not track_list:
+ return None
+ if len(track_list) > 1:
+ raise ApplicationError(f"Duplicate {track_id=}")
+ return track_list[0]
+
+
+def tracks_by_artist(filter_str: str) -> list[TrackDTO]:
+ """
+ Return tracks where artist is like filter
+ """
+
+ return _tracks_where(Tracks.artist.ilike(f"%{filter_str}%"))
+
+
+def tracks_by_title(filter_str: str) -> list[TrackDTO]:
+ """
+ Return tracks where title is like filter
+ """
+
+ return _tracks_where(Tracks.title.ilike(f"%{filter_str}%"))
+
+
+def get_all_tracks() -> list[TrackDTO]:
+ """Return a list of all tracks"""
+
+ return _tracks_where(Tracks.id > 0)
+
+
+@log_call
def add_track_to_header(playlistrow_id: int, track_id: int) -> None:
"""
Add a track to this (header) row
@@ -132,6 +267,7 @@ def add_track_to_header(playlistrow_id: int, track_id: int) -> None:
session.commit()
+@log_call
def create_track(path: str, metadata: dict[str, str | int | float]) -> TrackDTO:
"""
Create a track db entry from a track path and return the DTO
@@ -163,6 +299,7 @@ def create_track(path: str, metadata: dict[str, str | int | float]) -> TrackDTO:
return new_track
+@log_call
def update_track(
path: str, track_id: int, metadata: dict[str, str | int | float]
) -> TrackDTO:
@@ -192,183 +329,208 @@ def update_track(
return updated_track
-def get_all_tracks() -> list[TrackDTO]:
- """Return a list of all tracks"""
-
- return _tracks_where(Tracks.id > 0)
-
-
-def track_by_id(track_id: int) -> TrackDTO | None:
+@log_call
+def get_filtered_tracks(filter: Filter) -> list[TrackDTO]:
"""
- Return track with specified id
+ Return tracks matching filter
"""
- # Alias PlaydatesTable for subquery
- LatestPlaydate = aliased(Playdates)
+ # Create a base query
+ query = Tracks.id > 0
- # Subquery: latest playdate for each track
- latest_playdate_subq = (
- select(
- LatestPlaydate.track_id,
- func.max(LatestPlaydate.lastplayed).label("lastplayed"),
- )
- .group_by(LatestPlaydate.track_id)
- .subquery()
- )
+ # Path specification
+ if filter.path:
+ if filter.path_type == "contains":
+ query = query.where(Tracks.path.ilike(f"%{filter.path}%"))
+ elif filter.path_type == "excluding":
+ query = query.where(Tracks.path.notilike(f"%{filter.path}%"))
+ else:
+ raise ApplicationError(f"Can't process filter path ({filter=})")
- stmt = (
- select(
- Tracks.id.label("track_id"),
- Tracks.artist,
- Tracks.bitrate,
- Tracks.duration,
- Tracks.fade_at,
- Tracks.intro,
- Tracks.path,
- Tracks.silence_at,
- Tracks.start_gap,
- Tracks.title,
- latest_playdate_subq.c.lastplayed,
+ # Duration specification
+ seconds_duration = filter.duration_number
+ if filter.duration_unit == Config.FILTER_DURATION_MINUTES:
+ seconds_duration *= 60
+ elif filter.duration_unit != Config.FILTER_DURATION_SECONDS:
+ raise ApplicationError(f"Can't process filter duration ({filter=})")
+
+ if filter.duration_type == Config.FILTER_DURATION_LONGER:
+ query = query.where(Tracks.duration >= seconds_duration)
+ elif filter.duration_unit == Config.FILTER_DURATION_SHORTER:
+ query = query.where(Tracks.duration <= seconds_duration)
+ else:
+ raise ApplicationError(f"Can't process filter duration type ({filter=})")
+
+ # Process comparator
+ if filter.last_played_comparator == Config.FILTER_PLAYED_COMPARATOR_ANYTIME:
+ return _tracks_where(query, filter_by_last_played=False)
+
+ elif filter.last_played_comparator == Config.FILTER_PLAYED_COMPARATOR_NEVER:
+ return _tracks_where(query, filter_by_last_played=True, last_played_before=None)
+ else:
+ # Last played specification
+ now = dt.datetime.now()
+ # Set sensible default, and correct for Config.FILTER_PLAYED_COMPARATOR_ANYTIME
+ before = now
+ # If not ANYTIME, set 'before' appropriates
+ if filter.last_played_unit == Config.FILTER_PLAYED_DAYS:
+ before = now - dt.timedelta(days=filter.last_played_number)
+ elif filter.last_played_unit == Config.FILTER_PLAYED_WEEKS:
+ before = now - dt.timedelta(days=7 * filter.last_played_number)
+ elif filter.last_played_unit == Config.FILTER_PLAYED_MONTHS:
+ before = now - dt.timedelta(days=30 * filter.last_played_number)
+ elif filter.last_played_unit == Config.FILTER_PLAYED_YEARS:
+ before = now - dt.timedelta(days=365 * filter.last_played_number)
+ else:
+ raise ApplicationError("Can't determine last played criteria")
+
+ return _tracks_where(
+ query, filter_by_last_played=True, last_played_before=before
)
- .outerjoin(latest_playdate_subq, Tracks.id == latest_playdate_subq.c.track_id)
- .where(Tracks.id == track_id)
- )
+
+
+def set_track_intro(track_id: int, intro: int) -> None:
+ """
+ Set track intro time
+ """
with db.Session() as session:
- record = session.execute(stmt).one_or_none()
- if not record:
- return None
-
- dto = TrackDTO(
- artist=record.artist,
- bitrate=record.bitrate,
- duration=record.duration,
- fade_at=record.fade_at,
- intro=record.intro,
- lastplayed=record.lastplayed,
- path=record.path,
- silence_at=record.silence_at,
- start_gap=record.start_gap,
- title=record.title,
- track_id=record.track_id,
+ session.execute(
+ update(Tracks)
+ .where(Tracks.id == track_id)
+ .values(intro=intro)
)
- return dto
+ session.commit()
-def _tracks_where(where: BinaryExpression | ColumnElement[bool]) -> list[TrackDTO]:
+# Playlist functions
+@log_call
+def _playlists_where(
+ query: BinaryExpression | ColumnElement[bool],
+) -> list[PlaylistDTO]:
"""
- Return tracks selected by where
+ Return playlists selected by query
"""
- # Alias PlaydatesTable for subquery
- LatestPlaydate = aliased(Playdates)
+ stmt = select(
+ Playlists.favourite,
+ Playlists.is_template,
+ Playlists.id.label("playlist_id"),
+ Playlists.name,
+ Playlists.open,
+ ).where(query)
- # Subquery: latest playdate for each track
- latest_playdate_subq = (
- select(
- LatestPlaydate.track_id,
- func.max(LatestPlaydate.lastplayed).label("lastplayed"),
- )
- .group_by(LatestPlaydate.track_id)
- .subquery()
- )
-
- stmt = (
- select(
- Tracks.id.label("track_id"),
- Tracks.artist,
- Tracks.bitrate,
- Tracks.duration,
- Tracks.fade_at,
- Tracks.intro,
- Tracks.path,
- Tracks.silence_at,
- Tracks.start_gap,
- Tracks.title,
- latest_playdate_subq.c.lastplayed,
- )
- .outerjoin(latest_playdate_subq, Tracks.id == latest_playdate_subq.c.track_id)
- .where(where)
- )
-
- results: list[TrackDTO] = []
+ results: list[PlaylistDTO] = []
with db.Session() as session:
records = session.execute(stmt).all()
for record in records:
- dto = TrackDTO(
- artist=record.artist,
- bitrate=record.bitrate,
- duration=record.duration,
- fade_at=record.fade_at,
- intro=record.intro,
- lastplayed=record.lastplayed,
- path=record.path,
- silence_at=record.silence_at,
- start_gap=record.start_gap,
- title=record.title,
- track_id=record.track_id,
+ dto = PlaylistDTO(
+ favourite=record.favourite,
+ is_template=record.is_template,
+ playlist_id=record.playlist_id,
+ name=record.name,
+ open=record.open,
)
results.append(dto)
return results
-def track_with_path(path: str) -> bool:
+@log_call
+def playlist_by_id(playlist_id: int) -> PlaylistDTO | None:
"""
- Return True if a track with passed path exists, else False
+ Return playlist with specified id
+ """
+
+ playlist_list = _playlists_where(Playlists.id == playlist_id)
+ if not playlist_list:
+ return None
+ if len(playlist_list) > 1:
+ raise ApplicationError(f"Duplicate {playlist_id=}")
+ return playlist_list[0]
+
+
+def playlists_closed() -> list[PlaylistDTO]:
+ """
+ Return a list of closed playlists
+ """
+
+ return _playlists_where(Playlists.open.is_(False))
+
+
+def playlists_open() -> list[PlaylistDTO]:
+ """
+ Return a list of open playlists
+ """
+
+ return _playlists_where(Playlists.open.is_(True))
+
+
+def playlists_template_by_id(playlist_id: int) -> PlaylistDTO | None:
+ """
+ Return a list of closed playlists
+ """
+
+ playlist_list = _playlists_where(
+ Playlists.playlist_id == playlist_id, Playlists.is_template.is_(True)
+ )
+ if not playlist_list:
+ return None
+ if len(playlist_list) > 1:
+ raise ApplicationError(f"Duplicate {playlist_id=}")
+ return playlist_list[0]
+
+
+def playlists_templates() -> list[PlaylistDTO]:
+ """
+ Return a list of playlist templates
+ """
+
+ return _playlists_where(Playlists.is_template.is_(True))
+
+
+def get_all_playlists():
+ """Return all playlists"""
+
+ return _playlists_where(Playlists.id > 0)
+
+
+def delete_playlist(playlist_id: int) -> None:
+ """Delete playlist"""
+
+ with db.Session() as session:
+ query = session.get(Playlists, playlist_id)
+ session.delete(query)
+ session.commit()
+
+
+def save_as_template(playlist_id: int, template_name: str) -> None:
+ """
+ Save playlist as templated
+ """
+
+ new_template = create_playlist(template_name, 0, as_template=True)
+
+ copy_playlist(playlist_id, new_template.id)
+
+
+def playlist_rename(playlist_id: int, new_name: str) -> None:
+ """
+ Rename playlist
"""
with db.Session() as session:
- track = (
- session.execute(select(Tracks).where(Tracks.path == path))
- .scalars()
- .one_or_none()
+ session.execute(
+ update(Playlists)
+ .where(Playlists.id == playlist_id)
+ .values(name=new_name)
)
- return track is not None
+ session.commit()
-def tracks_like_artist(filter_str: str) -> list[TrackDTO]:
- """
- Return tracks where artist is like filter
- """
-
- return _tracks_where(Tracks.artist.ilike(f"%{filter_str}%"))
-
-
-def tracks_like_title(filter_str: str) -> list[TrackDTO]:
- """
- Return tracks where title is like filter
- """
-
- return _tracks_where(Tracks.title.ilike(f"%{filter_str}%"))
-
-
-def get_last_played_dates(track_id: int, limit: int = 5) -> str:
- """
- Return the most recent 'limit' dates that this track has been played
- as a text list
- """
-
- with db.Session() as session:
- playdates = session.scalars(
- Playdates.select()
- .where(Playdates.track_id == track_id)
- .order_by(Playdates.lastplayed.desc())
- .limit(limit)
- ).all()
-
- return "
".join(
- [
- a.lastplayed.strftime(Config.LAST_PLAYED_TOOLTIP_DATE_FORMAT)
- for a in playdates
- ]
- )
-
-
-# Playlist functions
def _check_playlist_integrity(
session: Session, playlist_id: int, fix: bool = False
) -> None:
@@ -401,6 +563,20 @@ def _check_playlist_integrity(
raise ApplicationError(msg)
+@log_call
+def playlist_mark_status(playlist_id: int, open: bool) -> None:
+ """Mark playlist as open or closed"""
+
+ with db.Session() as session:
+ session.execute(
+ update(Playlists)
+ .where(Playlists.id == playlist_id)
+ .values(open=open)
+ )
+
+ session.commit()
+
+
@log_call
def _shift_rows(
session: Session, playlist_id: int, starting_row: int, shift_by: int
@@ -512,15 +688,7 @@ def move_rows(
_check_playlist_integrity(session, to_playlist_id, fix=False)
-def update_playdates(track_id: int) -> None:
- """
- Update playdates for passed track
- """
-
- with db.Session() as session:
- _ = Playdates(session, track_id)
-
-
+@log_call
def update_row_numbers(
playlist_id: int, id_to_row_number: list[dict[int, int]]
) -> None:
@@ -537,7 +705,8 @@ def update_row_numbers(
_check_playlist_integrity(session, playlist_id, fix=False)
-def create_playlist(name: str, template_id: int) -> PlaylistDTO:
+@log_call
+def create_playlist(name: str, template_id: int, as_template: bool = False) -> PlaylistDTO:
"""
Create playlist and return DTO.
"""
@@ -545,11 +714,15 @@ def create_playlist(name: str, template_id: int) -> PlaylistDTO:
with db.Session() as session:
try:
playlist = Playlists(session, name, template_id)
+ playlist.is_template = as_template
playlist_id = playlist.id
session.commit()
except Exception:
raise ApplicationError("Can't create Playlist")
+ if template_id != 0:
+ copy_playlist(template_id, playlist_id)
+
new_playlist = playlist_by_id(playlist_id)
if not new_playlist:
raise ApplicationError("Can't retrieve new Playlist")
@@ -557,6 +730,7 @@ def create_playlist(name: str, template_id: int) -> PlaylistDTO:
return new_playlist
+@log_call
def get_playlist_row(playlistrow_id: int) -> PlaylistRowDTO | None:
"""
Return specific row DTO
@@ -746,6 +920,40 @@ def get_playlist_rows(
return dto_list
+def copy_playlist(src_id: int, dst_id: int) -> None:
+ """Copy playlist entries"""
+
+ with db.Session() as session:
+ src_rows = session.scalars(
+ select(PlaylistRows).filter(PlaylistRows.playlist_id == src_id)
+ ).all()
+
+ for plr in src_rows:
+ PlaylistRows(
+ session=session,
+ playlist_id=dst_id,
+ row_number=plr.row_number,
+ note=plr.note,
+ track_id=plr.track_id,
+ )
+
+
+def playlist_row_count(playlist_id: int) -> int:
+ """
+ Return number of rows in playlist
+ """
+
+ with db.Session() as session:
+ count = session.scalar(
+ select(func.count())
+ .select_from(PlaylistRows)
+ .where(PlaylistRows.playlist_id == playlist_id)
+ )
+
+ return count
+
+
+@log_call
def insert_row(
playlist_id: int, row_number: int, track_id: int | None, note: str
) -> PlaylistRowDTO:
@@ -825,33 +1033,220 @@ def remove_rows(playlist_id: int, row_numbers: list[int]) -> None:
session.commit()
-def playlist_by_id(playlist_id: int) -> PlaylistDTO | None:
+@log_call
+def update_template_favourite(template_id: int, favourite: bool) -> None:
+ """Update template favourite"""
+
+ with db.Session() as session:
+ session.execute(
+ update(Playlists)
+ .where(Playlists.id == template_id)
+ .values(favourite=favourite)
+ )
+ session.commit()
+
+
+@log_call
+def playlist_save_tabs(playlist_id_to_tab: dict[int, int]) -> None:
"""
- Return playlist with specified id
+ Save the tab numbers of the open playlists.
+ """
+
+ with db.Session() as session:
+ # Clear all existing tab numbers
+ session.execute(
+ update(Playlists)
+ .where(Playlists.id.in_(playlist_id_to_tab.keys()))
+ .values(tab=None)
+ )
+ for (playlist_id, tab) in playlist_id_to_tab.items():
+ session.execute(
+ update(Playlists)
+ .where(Playlists.id == playlist_id)
+ .values(tab=tab)
+ )
+ session.commit()
+
+
+# Playdates
+@log_call
+def get_last_played_dates(track_id: int, limit: int = 5) -> str:
+ """
+ Return the most recent 'limit' dates that this track has been played
+ as a text list
+ """
+
+ with db.Session() as session:
+ playdates = session.scalars(
+ Playdates.select()
+ .where(Playdates.track_id == track_id)
+ .order_by(Playdates.lastplayed.desc())
+ .limit(limit)
+ ).all()
+
+ return "
".join(
+ [
+ a.lastplayed.strftime(Config.LAST_PLAYED_TOOLTIP_DATE_FORMAT)
+ for a in playdates
+ ]
+ )
+
+
+def update_playdates(track_id: int) -> None:
+ """
+ Update playdates for passed track
+ """
+
+ with db.Session() as session:
+ _ = Playdates(session, track_id)
+
+
+def playdates_between_dates(
+ start: dt.datetime, end: dt.datetime | None = None
+) -> list[PlaydatesDTO]:
+ """
+ Return a list of PlaydateDTO objects from between times (until now if end is None)
+ """
+
+ if end is None:
+ end = dt.datetime.now()
+
+ stmt = select(
+ Playdates.id.label("playdate_id"),
+ Playdates.lastplayed,
+ Playdates.track_id,
+ Playdates.track,
+ ).where(
+ Playdates.lastplayed >= start,
+ Playdates.lastplayed <= end
+ )
+
+ results: list[PlaydatesDTO] = []
+
+ with db.Session() as session:
+ records = session.execute(stmt).all()
+ for record in records:
+ dto = PlaydatesDTO(
+ playdate_id=record.playdate_id,
+ lastplayed=record.lastplayed,
+ track_id=record.track_id,
+ artist=record.track.artist,
+ bitrate=record.track.bitrate,
+ duration=record.track.duration,
+ fade_at=record.track.fade_at,
+ intro=record.track.intro,
+ path=record.track.path,
+ silence_at=record.track.silence_at,
+ start_gap=record.track.start_gap,
+ title=record.track.title,
+ )
+ results.append(dto)
+
+ return results
+
+
+# Queries
+@log_call
+def _queries_where(
+ query: BinaryExpression | ColumnElement[bool],
+) -> list[QueryDTO]:
+ """
+ Return queries selected by query
"""
stmt = select(
- Playlists.id.label("playlist_id"),
- Playlists.name,
- Playlists.favourite,
- Playlists.is_template,
- Playlists.open,
- ).where(Playlists.id == playlist_id)
+ Queries.id.label("query_id"), Queries.name, Queries.favourite, Queries.filter
+ ).where(query)
+
+ results: list[QueryDTO] = []
with db.Session() as session:
- record = session.execute(stmt).one_or_none()
- if not record:
- return None
+ records = session.execute(stmt).one_or_none()
+ for record in records:
+ dto = QueryDTO(
+ favourite=record.favourite,
+ filter=record.filter,
+ name=record.name,
+ query_id=record.query_id,
+ )
+ results.append(dto)
- dto = PlaylistDTO(
- name=record.name,
- playlist_id=record.playlist_id,
- favourite=record.favourite,
- is_template=record.is_template,
- open=record.open,
+ return results
+
+
+def get_all_queries(favourites_only: bool = False) -> list[QueryDTO]:
+ """Return a list of all queries"""
+
+ query = Queries.id > 0
+ return _queries_where(query)
+
+
+def query_by_id(query_id: int) -> QueryDTO | None:
+ """Return query"""
+
+ query_list = _queries_where(Queries.id == query_id)
+ if not query_list:
+ return None
+ if len(query_list) > 1:
+ raise ApplicationError(f"Duplicate {query_id=}")
+ return query_list[0]
+
+
+def update_query_filter(query_id: int, filter: Filter) -> None:
+ """Update query filter"""
+
+ with db.Session() as session:
+ session.execute(
+ update(Queries).where(Queries.id == query_id).values(filter=filter)
)
+ session.commit()
- return dto
+
+def delete_query(query_id: int) -> None:
+ """Delete query"""
+
+ with db.Session() as session:
+ query = session.get(Queries, query_id)
+ session.delete(query)
+ session.commit()
+
+
+def update_query_name(query_id: int, name: str) -> None:
+ """Update query name"""
+
+ with db.Session() as session:
+ session.execute(update(Queries).where(Queries.id == query_id).values(name=name))
+ session.commit()
+
+
+def update_query_favourite(query_id: int, favourite: bool) -> None:
+ """Update query favourite"""
+
+ with db.Session() as session:
+ session.execute(
+ update(Queries).where(Queries.id == query_id).values(favourite=favourite)
+ )
+ session.commit()
+
+
+def create_query(name: str, filter: Filter) -> QueryDTO:
+ """
+ Create a query and return the DTO
+ """
+
+ with db.Session() as session:
+ try:
+ query = Queries(session=session, name=name, filter=filter)
+ query_id = query.id
+ session.commit()
+ except Exception:
+ raise ApplicationError("Can't create Query")
+
+ new_query = query_by_id(query_id)
+ if not new_query:
+ raise ApplicationError("Unable to create new query")
+
+ return new_query
# Misc
@@ -889,3 +1284,14 @@ def set_setting(name: str, value: int) -> None:
raise ApplicationError("Can't create Settings record")
record.f_int = value
session.commit()
+
+
+def get_db_name() -> str:
+ """Return database name"""
+
+ with db.Session() as session:
+ if session.bind:
+ dbname = session.bind.engine.url.database
+ return dbname
+ return Config.DB_NOT_FOUND
+
diff --git a/app/utilities.py b/app/utilities.py
index 8c42a8a..9d297fd 100755
--- a/app/utilities.py
+++ b/app/utilities.py
@@ -5,7 +5,6 @@ import os
# PyQt imports
# Third party imports
-from sqlalchemy.orm.session import Session
# App imports
from config import Config
@@ -13,10 +12,10 @@ from helpers import (
get_tags,
)
from log import log
-from models import Tracks
+import repository
-def check_db(session: Session) -> None:
+def check_db() -> None:
"""
Database consistency check.
@@ -27,7 +26,7 @@ def check_db(session: Session) -> None:
Check all paths in database exist
"""
- db_paths = set([a.path for a in Tracks.get_all(session)])
+ db_paths = set([a.path for a in repository.get_all_tracks()])
os_paths_list = []
for root, _dirs, files in os.walk(Config.ROOT):
@@ -52,7 +51,7 @@ def check_db(session: Session) -> None:
missing_file_count += 1
- track = Tracks.get_by_path(session, path)
+ track = repository.track_by_path(path)
if not track:
# This shouldn't happen as we're looking for paths in
# database that aren't in filesystem, but just in case...
@@ -74,7 +73,7 @@ def check_db(session: Session) -> None:
for t in paths_not_found:
print(
f"""
- Track ID: {t.id}
+ Track ID: {t.track_id}
Path: {t.path}
Title: {t.title}
Artist: {t.artist}
@@ -84,14 +83,15 @@ def check_db(session: Session) -> None:
print("There were more paths than listed that were not found")
-def update_bitrates(session: Session) -> None:
+def update_bitrates() -> None:
"""
Update bitrates on all tracks in database
"""
- for track in Tracks.get_all(session):
+ for track in repository.get_all_tracks():
try:
t = get_tags(track.path)
+ # TODO this won't persist as we're updating DTO
track.bitrate = t.bitrate
except FileNotFoundError:
continue
diff --git a/tests/test_repository.py b/tests/test_repository.py
index a210f53..e94999b 100644
--- a/tests/test_repository.py
+++ b/tests/test_repository.py
@@ -131,10 +131,10 @@ class MyTestCase(unittest.TestCase):
_ = repository.create_track(self.isa_path, metadata)
metadata = get_all_track_metadata(self.mom_path)
_ = repository.create_track(self.mom_path, metadata)
- result_isa = repository.tracks_like_title(self.isa_title)
+ result_isa = repository.tracks_by_title(self.isa_title)
assert len(result_isa) == 1
assert result_isa[0].title == self.isa_title
- result_mom = repository.tracks_like_title(self.mom_title)
+ result_mom = repository.tracks_by_title(self.mom_title)
assert len(result_mom) == 1
assert result_mom[0].title == self.mom_title
diff --git a/tests/test_ui.py b/tests/test_ui.py
index 508c700..0cfded6 100644
--- a/tests/test_ui.py
+++ b/tests/test_ui.py
@@ -132,9 +132,8 @@ class MyTestCase(unittest.TestCase):
Config.ROOT = os.path.join(os.path.dirname(__file__), "testdata")
- with db.Session() as session:
- utilities.check_db(session)
- utilities.update_bitrates(session)
+ utilities.check_db()
+ utilities.update_bitrates()
# def test_meta_all_clear(qtbot, session):