Compare commits

...

4 Commits

Author SHA1 Message Date
Keith Edmunds
8e48d63ebb WIP: queries management
Menus and management working. Wrong tracks showing up in queries.
2025-03-02 19:14:53 +00:00
Keith Edmunds
aa6ab03555 Make manage queries and manage templates into classes 2025-02-28 11:25:29 +00:00
Keith Edmunds
90d72464cb Clean up handling of separators in dynamic menu 2025-02-27 08:13:29 +00:00
Keith Edmunds
82e707a6f6 Make filter field in queries table non-nullable 2025-02-27 08:12:48 +00:00
9 changed files with 669 additions and 257 deletions

View File

@ -80,6 +80,7 @@ class FileErrors(NamedTuple):
@dataclass @dataclass
class Filter: class Filter:
version: int = 1
path_type: str = "contains" path_type: str = "contains"
path: Optional[str] = None path: Optional[str] = None
last_played_number: Optional[int] = None last_played_number: Optional[int] = None

View File

@ -49,6 +49,18 @@ class Config(object):
FADEOUT_DB = -10 FADEOUT_DB = -10
FADEOUT_SECONDS = 5 FADEOUT_SECONDS = 5
FADEOUT_STEPS_PER_SECOND = 5 FADEOUT_STEPS_PER_SECOND = 5
FILTER_DURATION_LONGER = "longer than"
FILTER_DURATION_MINUTES = "minutes"
FILTER_DURATION_SECONDS = "seconds"
FILTER_DURATION_SHORTER = "shorter than"
FILTER_PATH_CONTAINS = "contains"
FILTER_PATH_EXCLUDING = "excluding"
FILTER_PLAYED_BEFORE = "before"
FILTER_PLAYED_DAYS = "days"
FILTER_PLAYED_MONTHS = "months"
FILTER_PLAYED_NEVER = "never"
FILTER_PLAYED_WEEKS = "weeks"
FILTER_PLAYED_YEARS = "years"
FUZZYMATCH_MINIMUM_LIST = 60.0 FUZZYMATCH_MINIMUM_LIST = 60.0
FUZZYMATCH_MINIMUM_SELECT_ARTIST = 80.0 FUZZYMATCH_MINIMUM_SELECT_ARTIST = 80.0
FUZZYMATCH_MINIMUM_SELECT_TITLE = 80.0 FUZZYMATCH_MINIMUM_SELECT_TITLE = 80.0

View File

@ -154,7 +154,7 @@ class QueriesTable(Model):
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(128), nullable=False) name: Mapped[str] = mapped_column(String(128), nullable=False)
_filter_data: Mapped[dict | None] = mapped_column("filter_data", JSONEncodedDict, nullable=True) _filter_data: Mapped[dict | None] = mapped_column("filter_data", JSONEncodedDict, nullable=False)
favourite: Mapped[bool] = mapped_column(Boolean, nullable=False, index=False, default=False) favourite: Mapped[bool] = mapped_column(Boolean, nullable=False, index=False, default=False)
def _get_filter(self) -> Filter: def _get_filter(self) -> Filter:

View File

@ -10,7 +10,7 @@ import ssl
import tempfile import tempfile
# PyQt imports # PyQt imports
from PyQt6.QtWidgets import QMainWindow, QMessageBox, QWidget from PyQt6.QtWidgets import QInputDialog, QMainWindow, QMessageBox, QWidget
# Third party imports # Third party imports
from mutagen.flac import FLAC # type: ignore from mutagen.flac import FLAC # type: ignore
@ -150,6 +150,23 @@ def get_audio_metadata(filepath: str) -> AudioMetadata:
) )
def get_name(prompt: str, default: str = "") -> str | None:
"""Get a name from the user"""
dlg = QInputDialog()
dlg.setInputMode(QInputDialog.InputMode.TextInput)
dlg.setLabelText(prompt)
while True:
if default:
dlg.setTextValue(default)
dlg.resize(500, 100)
ok = dlg.exec()
if ok:
return dlg.textValue()
return None
def get_relative_date( def get_relative_date(
past_date: Optional[dt.datetime], reference_date: Optional[dt.datetime] = None past_date: Optional[dt.datetime], reference_date: Optional[dt.datetime] = None
) -> str: ) -> str:

View File

@ -4,10 +4,10 @@ menus:
- text: "Save as Template" - text: "Save as Template"
handler: "save_as_template" handler: "save_as_template"
- text: "Manage Templates" - text: "Manage Templates"
handler: "manage_templates" handler: "manage_templates_wrapper"
- separator: true - separator: true
- text: "Manage Queries" - text: "Manage Queries"
handler: "manage_queries" handler: "manage_queries_wrapper"
- separator: true - separator: true
- text: "Exit" - text: "Exit"
handler: "close" handler: "close"

View File

@ -25,7 +25,7 @@ from sqlalchemy.orm.session import Session
from sqlalchemy.engine.row import RowMapping from sqlalchemy.engine.row import RowMapping
# App imports # App imports
from classes import ApplicationError from classes import ApplicationError, Filter
from config import Config from config import Config
from dbmanager import DatabaseManager from dbmanager import DatabaseManager
import dbtables import dbtables
@ -610,11 +610,21 @@ class Queries(dbtables.QueriesTable):
session.commit() session.commit()
@classmethod @classmethod
def get_all_queries(cls, session: Session) -> Sequence["Queries"]: def get_all(cls, session: Session) -> Sequence["Queries"]:
"""Returns a list of all queries ordered by name""" """Returns a list of all queries ordered by name"""
return session.scalars(select(cls).order_by(cls.name)).all() return session.scalars(select(cls).order_by(cls.name)).all()
@classmethod
def get_favourites(cls, session: Session) -> Sequence["Queries"]:
"""Returns a list of favourite queries ordered by name"""
return session.scalars(
select(cls)
.where(cls.favourite.is_(True))
.order_by(cls.name)
).all()
class Settings(dbtables.SettingsTable): class Settings(dbtables.SettingsTable):
def __init__(self, session: Session, name: str) -> None: def __init__(self, session: Session, name: str) -> None:
@ -700,6 +710,40 @@ class Tracks(dbtables.TracksTable):
.all() .all()
) )
@classmethod
def get_filtered_tracks(cls, session: Session, filter: Filter) -> Sequence["Tracks"]:
"""
Return tracks matching filter
"""
query = select(cls)
if filter.path:
if filter.path_type == "contains":
query = query.where(cls.path.ilike(f"%{filter.path}%"))
elif filter.path_type == "excluding":
query = query.where(cls.path.notilike(f"%{filter.path}%"))
else:
raise ApplicationError(f"Can't process filter path ({filter=})")
# TODO
# if last_played_number:
# need group_by track_id and having max/min lastplayed gt/lt, etc
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(cls.duration >= seconds_duration)
elif filter.duration_unit == Config.FILTER_DURATION_SHORTER:
query = query.where(cls.duration <= seconds_duration)
else:
raise ApplicationError(f"Can't process filter duration type ({filter=})")
records = session.scalars(
query).unique().all()
return records
@classmethod @classmethod
def get_by_path(cls, session: Session, path: str) -> Optional["Tracks"]: def get_by_path(cls, session: Session, path: str) -> Optional["Tracks"]:
""" """

File diff suppressed because it is too large Load Diff

View File

@ -38,7 +38,7 @@ from helpers import (
show_warning, show_warning,
) )
from log import log from log import log
from models import db, Playdates from models import db, Playdates, Tracks
from music_manager import RowAndTrack from music_manager import RowAndTrack
@ -228,20 +228,20 @@ class QuerylistModel(QAbstractTableModel):
row = 0 row = 0
try: try:
results = Tracks.get_filtered(self.session, self.filter) results = Tracks.get_filtered_tracks(self.session, self.filter)
for result in results: for result in results:
if hasattr(result, "lastplayed"): if hasattr(result, "lastplayed"):
lastplayed = result["lastplayed"] lastplayed = result["lastplayed"]
else: else:
lastplayed = None lastplayed = None
queryrow = QueryRow( queryrow = QueryRow(
artist=result["artist"], artist=result.artist,
bitrate=result["bitrate"], bitrate=result.bitrate or 0,
duration=result["duration"], duration=result.duration,
lastplayed=lastplayed, lastplayed=lastplayed,
path=result["path"], path=result.path,
title=result["title"], title=result.title,
track_id=result["id"], track_id=result.id,
) )
self.querylist_rows[row] = queryrow self.querylist_rows[row] = queryrow

View File

@ -33,7 +33,7 @@ def upgrade_() -> None:
op.create_table('queries', op.create_table('queries',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('name', sa.String(length=128), nullable=False), sa.Column('name', sa.String(length=128), nullable=False),
sa.Column('filter_data', dbtables.JSONEncodedDict(), nullable=True), sa.Column('filter_data', dbtables.JSONEncodedDict(), nullable=False),
sa.Column('favourite', sa.Boolean(), nullable=False), sa.Column('favourite', sa.Boolean(), nullable=False),
sa.PrimaryKeyConstraint('id') sa.PrimaryKeyConstraint('id')
) )