Migrate to Alchemical
This commit is contained in:
parent
6890e0d0c2
commit
9d44642fea
@ -1,13 +1,18 @@
|
|||||||
|
# Standard library imports
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import datetime as dt
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
import datetime as dt
|
||||||
|
|
||||||
|
# PyQt imports
|
||||||
from PyQt6.QtCore import pyqtSignal, QObject, QThread
|
from PyQt6.QtCore import pyqtSignal, QObject, QThread
|
||||||
|
|
||||||
|
# Third party imports
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pyqtgraph as pg # type: ignore
|
import pyqtgraph as pg # type: ignore
|
||||||
|
from sqlalchemy.orm import scoped_session
|
||||||
|
|
||||||
|
# App imports
|
||||||
from config import Config
|
from config import Config
|
||||||
from dbconfig import scoped_session
|
|
||||||
from models import PlaylistRows
|
from models import PlaylistRows
|
||||||
import helpers
|
import helpers
|
||||||
|
|
||||||
|
|||||||
@ -1,38 +0,0 @@
|
|||||||
import inspect
|
|
||||||
import os
|
|
||||||
from config import Config
|
|
||||||
from contextlib import contextmanager
|
|
||||||
from sqlalchemy import create_engine
|
|
||||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
|
||||||
from typing import Generator
|
|
||||||
|
|
||||||
from log import log
|
|
||||||
|
|
||||||
MYSQL_CONNECT = os.environ.get("MM_DB")
|
|
||||||
if MYSQL_CONNECT is None:
|
|
||||||
raise ValueError("MYSQL_CONNECT is undefined")
|
|
||||||
else:
|
|
||||||
dbname = MYSQL_CONNECT.split("/")[-1]
|
|
||||||
log.debug(f"Database: {dbname}")
|
|
||||||
|
|
||||||
engine = create_engine(
|
|
||||||
MYSQL_CONNECT,
|
|
||||||
echo=Config.DISPLAY_SQL,
|
|
||||||
pool_pre_ping=True,
|
|
||||||
future=True,
|
|
||||||
connect_args={"charset": "utf8mb4"},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def Session() -> Generator[scoped_session, None, None]:
|
|
||||||
frame = inspect.stack()[2]
|
|
||||||
file = frame.filename
|
|
||||||
function = frame.function
|
|
||||||
lineno = frame.lineno
|
|
||||||
Session = scoped_session(sessionmaker(bind=engine))
|
|
||||||
log.debug(f"Session acquired: {file}:{function}:{lineno} " f"[{hex(id(Session))}]")
|
|
||||||
yield Session
|
|
||||||
log.debug(f" Session released [{hex(id(Session))}]")
|
|
||||||
Session.commit()
|
|
||||||
Session.close()
|
|
||||||
187
app/dbtables.py
Normal file
187
app/dbtables.py
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
# Standard library imports
|
||||||
|
from typing import List, Optional
|
||||||
|
import datetime as dt
|
||||||
|
import os
|
||||||
|
|
||||||
|
# PyQt imports
|
||||||
|
|
||||||
|
# Third party imports
|
||||||
|
from alchemical import Alchemical, Model # type: ignore
|
||||||
|
from sqlalchemy import (
|
||||||
|
Boolean,
|
||||||
|
DateTime,
|
||||||
|
ForeignKey,
|
||||||
|
String,
|
||||||
|
)
|
||||||
|
from sqlalchemy.ext.associationproxy import association_proxy
|
||||||
|
from sqlalchemy.orm import (
|
||||||
|
Mapped,
|
||||||
|
mapped_column,
|
||||||
|
relationship,
|
||||||
|
)
|
||||||
|
|
||||||
|
# App imports
|
||||||
|
|
||||||
|
|
||||||
|
# Database classes
|
||||||
|
# Note: initialisation of the 'db' variable is at the foot of this
|
||||||
|
# module.
|
||||||
|
class CartsTable(Model):
|
||||||
|
__tablename__ = "carts"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||||
|
cart_number: Mapped[int] = mapped_column(unique=True)
|
||||||
|
name: Mapped[str] = mapped_column(String(256), index=True)
|
||||||
|
duration: Mapped[Optional[int]] = mapped_column(index=True)
|
||||||
|
path: Mapped[Optional[str]] = mapped_column(String(2048), index=False)
|
||||||
|
enabled: Mapped[Optional[bool]] = mapped_column(default=False)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return (
|
||||||
|
f"<Carts(id={self.id}, cart={self.cart_number}, "
|
||||||
|
f"name={self.name}, path={self.path}>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NoteColoursTable(Model):
|
||||||
|
__tablename__ = "notecolours"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||||
|
substring: Mapped[str] = mapped_column(String(256), index=False)
|
||||||
|
colour: Mapped[str] = mapped_column(String(21), index=False)
|
||||||
|
enabled: Mapped[bool] = mapped_column(default=True, index=True)
|
||||||
|
is_regex: Mapped[bool] = mapped_column(default=False, index=False)
|
||||||
|
is_casesensitive: Mapped[bool] = mapped_column(default=False, index=False)
|
||||||
|
order: Mapped[Optional[int]] = mapped_column(index=True)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return (
|
||||||
|
f"<NoteColour(id={self.id}, substring={self.substring}, "
|
||||||
|
f"colour={self.colour}>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PlaydatesTable(Model):
|
||||||
|
__tablename__ = "playdates"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||||
|
lastplayed: Mapped[dt.datetime] = mapped_column(index=True)
|
||||||
|
track_id: Mapped[int] = mapped_column(ForeignKey("tracks.id"))
|
||||||
|
track: Mapped["TracksTable"] = relationship("Tracks", back_populates="playdates")
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return (
|
||||||
|
f"<Playdates(id={self.id}, track_id={self.track_id} "
|
||||||
|
f"lastplayed={self.lastplayed}>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PlaylistsTable(Model):
|
||||||
|
"""
|
||||||
|
Manage playlists
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "playlists"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||||
|
name: Mapped[str] = mapped_column(String(32), unique=True)
|
||||||
|
last_used: Mapped[Optional[dt.datetime]] = mapped_column(DateTime, default=None)
|
||||||
|
tab: Mapped[Optional[int]] = mapped_column(default=None)
|
||||||
|
open: Mapped[bool] = mapped_column(default=False)
|
||||||
|
is_template: Mapped[bool] = mapped_column(default=False)
|
||||||
|
deleted: Mapped[bool] = mapped_column(default=False)
|
||||||
|
rows: Mapped[List["PlaylistRowsTable"]] = relationship(
|
||||||
|
"PlaylistRows",
|
||||||
|
back_populates="playlist",
|
||||||
|
cascade="all, delete-orphan",
|
||||||
|
order_by="PlaylistRows.plr_rownum",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return (
|
||||||
|
f"<Playlists(id={self.id}, name={self.name}, "
|
||||||
|
f"is_templatee={self.is_template}, open={self.open}>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PlaylistRowsTable(Model):
|
||||||
|
__tablename__ = "playlist_rows"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||||
|
plr_rownum: Mapped[int]
|
||||||
|
note: Mapped[str] = mapped_column(
|
||||||
|
String(2048), index=False, default="", nullable=False
|
||||||
|
)
|
||||||
|
playlist_id: Mapped[int] = mapped_column(ForeignKey("playlists.id"))
|
||||||
|
playlist: Mapped[PlaylistsTable] = relationship(back_populates="rows")
|
||||||
|
track_id: Mapped[Optional[int]] = mapped_column(ForeignKey("tracks.id"))
|
||||||
|
track: Mapped["TracksTable"] = relationship(
|
||||||
|
"Tracks",
|
||||||
|
back_populates="playlistrows",
|
||||||
|
)
|
||||||
|
played: Mapped[bool] = mapped_column(
|
||||||
|
Boolean, nullable=False, index=False, default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return (
|
||||||
|
f"<PlaylistRow(id={self.id}, playlist_id={self.playlist_id}, "
|
||||||
|
f"track_id={self.track_id}, "
|
||||||
|
f"note={self.note}, plr_rownum={self.plr_rownum}>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsTable(Model):
|
||||||
|
"""Manage settings"""
|
||||||
|
|
||||||
|
__tablename__ = "settings"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||||
|
name: Mapped[str] = mapped_column(String(64), unique=True)
|
||||||
|
f_datetime: Mapped[Optional[dt.datetime]] = mapped_column(default=None)
|
||||||
|
f_int: Mapped[Optional[int]] = mapped_column(default=None)
|
||||||
|
f_string: Mapped[Optional[str]] = mapped_column(String(128), default=None)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return (
|
||||||
|
f"<Settings(id={self.id}, name={self.name}, "
|
||||||
|
f"f_datetime={self.f_datetime}, f_int={self.f_int}, f_string={self.f_string}>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TracksTable(Model):
|
||||||
|
__tablename__ = "tracks"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||||
|
title: Mapped[str] = mapped_column(String(256), index=True)
|
||||||
|
artist: Mapped[str] = mapped_column(String(256), index=True)
|
||||||
|
bitrate: Mapped[Optional[int]] = mapped_column(default=None)
|
||||||
|
duration: Mapped[int] = mapped_column(index=True)
|
||||||
|
fade_at: Mapped[int] = mapped_column(index=False)
|
||||||
|
mtime: Mapped[float] = mapped_column(index=True)
|
||||||
|
path: Mapped[str] = mapped_column(String(2048), index=False, unique=True)
|
||||||
|
silence_at: Mapped[int] = mapped_column(index=False)
|
||||||
|
start_gap: Mapped[int] = mapped_column(index=False)
|
||||||
|
playlistrows: Mapped[List[PlaylistRowsTable]] = relationship(
|
||||||
|
"PlaylistRows", back_populates="track"
|
||||||
|
)
|
||||||
|
playlists = association_proxy("playlistrows", "playlist")
|
||||||
|
playdates: Mapped[List[PlaydatesTable]] = relationship(
|
||||||
|
"Playdates",
|
||||||
|
back_populates="track",
|
||||||
|
lazy="joined",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return (
|
||||||
|
f"<Track(id={self.id}, title={self.title}, "
|
||||||
|
f"artist={self.artist}, path={self.path}>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
MYSQL_CONNECT = os.environ.get("MM_DB")
|
||||||
|
if MYSQL_CONNECT is None:
|
||||||
|
raise ValueError("MYSQL_CONNECT is undefined")
|
||||||
|
else:
|
||||||
|
dbname = MYSQL_CONNECT.split("/")[-1]
|
||||||
|
db = Alchemical(MYSQL_CONNECT)
|
||||||
@ -1,10 +1,17 @@
|
|||||||
|
# Standard library imports
|
||||||
|
|
||||||
|
# PyQt imports
|
||||||
|
|
||||||
|
# Third party imports
|
||||||
|
|
||||||
|
# App imports
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from PyQt6.QtCore import QEvent, Qt
|
from PyQt6.QtCore import QEvent, Qt
|
||||||
from PyQt6.QtWidgets import QDialog, QListWidgetItem
|
from PyQt6.QtWidgets import QDialog, QListWidgetItem
|
||||||
|
|
||||||
from classes import MusicMusterSignals
|
from classes import MusicMusterSignals
|
||||||
from dbconfig import scoped_session
|
from sqlalchemy.orm import scoped_session
|
||||||
from helpers import (
|
from helpers import (
|
||||||
ask_yes_no,
|
ask_yes_no,
|
||||||
get_relative_date,
|
get_relative_date,
|
||||||
|
|||||||
189
app/models.py
189
app/models.py
@ -1,63 +1,37 @@
|
|||||||
#!/usr/bin/python3
|
# Standard library imports
|
||||||
|
from typing import List, Optional, Sequence
|
||||||
|
import datetime as dt
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from config import Config
|
# PyQt imports
|
||||||
from dbconfig import scoped_session
|
|
||||||
|
|
||||||
import datetime as dt
|
|
||||||
from typing import List, Optional, Sequence
|
|
||||||
|
|
||||||
from sqlalchemy.ext.associationproxy import association_proxy
|
|
||||||
|
|
||||||
|
# Third party imports
|
||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
bindparam,
|
bindparam,
|
||||||
Boolean,
|
|
||||||
DateTime,
|
|
||||||
delete,
|
delete,
|
||||||
ForeignKey,
|
|
||||||
func,
|
func,
|
||||||
select,
|
select,
|
||||||
String,
|
|
||||||
update,
|
update,
|
||||||
)
|
)
|
||||||
|
|
||||||
from sqlalchemy.orm import (
|
|
||||||
DeclarativeBase,
|
|
||||||
joinedload,
|
|
||||||
Mapped,
|
|
||||||
mapped_column,
|
|
||||||
relationship,
|
|
||||||
)
|
|
||||||
from sqlalchemy.orm.exc import (
|
|
||||||
NoResultFound,
|
|
||||||
)
|
|
||||||
from sqlalchemy.exc import (
|
from sqlalchemy.exc import (
|
||||||
IntegrityError,
|
IntegrityError,
|
||||||
)
|
)
|
||||||
|
from sqlalchemy.orm import (
|
||||||
|
joinedload,
|
||||||
|
scoped_session,
|
||||||
|
)
|
||||||
|
from sqlalchemy.orm.exc import (
|
||||||
|
NoResultFound,
|
||||||
|
)
|
||||||
|
|
||||||
|
# App imports
|
||||||
|
import dbtables
|
||||||
|
from config import Config
|
||||||
from log import log
|
from log import log
|
||||||
|
|
||||||
|
|
||||||
class Base(DeclarativeBase):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# Database classes
|
# Database classes
|
||||||
class Carts(Base):
|
class Carts(dbtables.CartsTable):
|
||||||
__tablename__ = "carts"
|
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
|
||||||
cart_number: Mapped[int] = mapped_column(unique=True)
|
|
||||||
name: Mapped[str] = mapped_column(String(256), index=True)
|
|
||||||
duration: Mapped[Optional[int]] = mapped_column(index=True)
|
|
||||||
path: Mapped[Optional[str]] = mapped_column(String(2048), index=False)
|
|
||||||
enabled: Mapped[Optional[bool]] = mapped_column(default=False)
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return (
|
|
||||||
f"<Carts(id={self.id}, cart={self.cart_number}, "
|
|
||||||
f"name={self.name}, path={self.path}>"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -80,22 +54,7 @@ class Carts(Base):
|
|||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
class NoteColours(Base):
|
class NoteColours(dbtables.NoteColoursTable):
|
||||||
__tablename__ = "notecolours"
|
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
|
||||||
substring: Mapped[str] = mapped_column(String(256), index=False)
|
|
||||||
colour: Mapped[str] = mapped_column(String(21), index=False)
|
|
||||||
enabled: Mapped[bool] = mapped_column(default=True, index=True)
|
|
||||||
is_regex: Mapped[bool] = mapped_column(default=False, index=False)
|
|
||||||
is_casesensitive: Mapped[bool] = mapped_column(default=False, index=False)
|
|
||||||
order: Mapped[Optional[int]] = mapped_column(index=True)
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return (
|
|
||||||
f"<NoteColour(id={self.id}, substring={self.substring}, "
|
|
||||||
f"colour={self.colour}>"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -157,19 +116,7 @@ class NoteColours(Base):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class Playdates(Base):
|
class Playdates(dbtables.PlaydatesTable):
|
||||||
__tablename__ = "playdates"
|
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
|
||||||
lastplayed: Mapped[dt.datetime] = mapped_column(index=True)
|
|
||||||
track_id: Mapped[int] = mapped_column(ForeignKey("tracks.id"))
|
|
||||||
track: Mapped["Tracks"] = relationship("Tracks", back_populates="playdates")
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return (
|
|
||||||
f"<Playdates(id={self.id}, track_id={self.track_id} "
|
|
||||||
f"lastplayed={self.lastplayed}>"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, session: scoped_session, track_id: int) -> None:
|
def __init__(self, session: scoped_session, track_id: int) -> None:
|
||||||
"""Record that track was played"""
|
"""Record that track was played"""
|
||||||
@ -208,32 +155,7 @@ class Playdates(Base):
|
|||||||
).all()
|
).all()
|
||||||
|
|
||||||
|
|
||||||
class Playlists(Base):
|
class Playlists(dbtables.PlaylistsTable):
|
||||||
"""
|
|
||||||
Manage playlists
|
|
||||||
"""
|
|
||||||
|
|
||||||
__tablename__ = "playlists"
|
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
|
||||||
name: Mapped[str] = mapped_column(String(32), unique=True)
|
|
||||||
last_used: Mapped[Optional[dt.datetime]] = mapped_column(DateTime, default=None)
|
|
||||||
tab: Mapped[Optional[int]] = mapped_column(default=None)
|
|
||||||
open: Mapped[bool] = mapped_column(default=False)
|
|
||||||
is_template: Mapped[bool] = mapped_column(default=False)
|
|
||||||
deleted: Mapped[bool] = mapped_column(default=False)
|
|
||||||
rows: Mapped[List["PlaylistRows"]] = relationship(
|
|
||||||
"PlaylistRows",
|
|
||||||
back_populates="playlist",
|
|
||||||
cascade="all, delete-orphan",
|
|
||||||
order_by="PlaylistRows.plr_rownum",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return (
|
|
||||||
f"<Playlists(id={self.id}, name={self.name}, "
|
|
||||||
f"is_templatee={self.is_template}, open={self.open}>"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, session: scoped_session, name: str):
|
def __init__(self, session: scoped_session, name: str):
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -366,31 +288,7 @@ class Playlists(Base):
|
|||||||
PlaylistRows.copy_playlist(session, playlist_id, template.id)
|
PlaylistRows.copy_playlist(session, playlist_id, template.id)
|
||||||
|
|
||||||
|
|
||||||
class PlaylistRows(Base):
|
class PlaylistRows(dbtables.PlaylistRowsTable):
|
||||||
__tablename__ = "playlist_rows"
|
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
|
||||||
plr_rownum: Mapped[int]
|
|
||||||
note: Mapped[str] = mapped_column(
|
|
||||||
String(2048), index=False, default="", nullable=False
|
|
||||||
)
|
|
||||||
playlist_id: Mapped[int] = mapped_column(ForeignKey("playlists.id"))
|
|
||||||
playlist: Mapped[Playlists] = relationship(back_populates="rows")
|
|
||||||
track_id: Mapped[Optional[int]] = mapped_column(ForeignKey("tracks.id"))
|
|
||||||
track: Mapped["Tracks"] = relationship(
|
|
||||||
"Tracks",
|
|
||||||
back_populates="playlistrows",
|
|
||||||
)
|
|
||||||
played: Mapped[bool] = mapped_column(
|
|
||||||
Boolean, nullable=False, index=False, default=False
|
|
||||||
)
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return (
|
|
||||||
f"<PlaylistRow(id={self.id}, playlist_id={self.playlist_id}, "
|
|
||||||
f"track_id={self.track_id}, "
|
|
||||||
f"note={self.note}, plr_rownum={self.plr_rownum}>"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -669,27 +567,7 @@ class PlaylistRows(Base):
|
|||||||
session.connection().execute(stmt, sqla_map)
|
session.connection().execute(stmt, sqla_map)
|
||||||
|
|
||||||
|
|
||||||
class Settings(Base):
|
class Settings(dbtables.SettingsTable):
|
||||||
"""Manage settings"""
|
|
||||||
|
|
||||||
__tablename__ = "settings"
|
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
|
||||||
name: Mapped[str] = mapped_column(String(64), unique=True)
|
|
||||||
f_datetime: Mapped[Optional[dt.datetime]] = mapped_column(default=None)
|
|
||||||
f_int: Mapped[Optional[int]] = mapped_column(default=None)
|
|
||||||
f_string: Mapped[Optional[str]] = mapped_column(String(128), default=None)
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return (
|
|
||||||
f"<Settings(id={self.id}, name={self.name}, "
|
|
||||||
f"f_datetime={self.f_datetime}, f_int={self.f_int}, f_string={self.f_string}>"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, session: scoped_session, name: str):
|
|
||||||
self.name = name
|
|
||||||
session.add(self)
|
|
||||||
session.flush()
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def all_as_dict(cls, session):
|
def all_as_dict(cls, session):
|
||||||
@ -722,28 +600,7 @@ class Settings(Base):
|
|||||||
session.flush()
|
session.flush()
|
||||||
|
|
||||||
|
|
||||||
class Tracks(Base):
|
class Tracks(dbtables.TracksTable):
|
||||||
__tablename__ = "tracks"
|
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
|
||||||
title: Mapped[str] = mapped_column(String(256), index=True)
|
|
||||||
artist: Mapped[str] = mapped_column(String(256), index=True)
|
|
||||||
bitrate: Mapped[Optional[int]] = mapped_column(default=None)
|
|
||||||
duration: Mapped[int] = mapped_column(index=True)
|
|
||||||
fade_at: Mapped[int] = mapped_column(index=False)
|
|
||||||
mtime: Mapped[float] = mapped_column(index=True)
|
|
||||||
path: Mapped[str] = mapped_column(String(2048), index=False, unique=True)
|
|
||||||
silence_at: Mapped[int] = mapped_column(index=False)
|
|
||||||
start_gap: Mapped[int] = mapped_column(index=False)
|
|
||||||
playlistrows: Mapped[List[PlaylistRows]] = relationship(
|
|
||||||
"PlaylistRows", back_populates="track"
|
|
||||||
)
|
|
||||||
playlists = association_proxy("playlistrows", "playlist")
|
|
||||||
playdates: Mapped[List[Playdates]] = relationship(
|
|
||||||
"Playdates",
|
|
||||||
back_populates="track",
|
|
||||||
lazy="joined",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,22 +1,17 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import datetime as dt
|
# Standard library imports
|
||||||
from time import sleep
|
|
||||||
from typing import (
|
|
||||||
cast,
|
|
||||||
List,
|
|
||||||
Optional,
|
|
||||||
)
|
|
||||||
from os.path import basename
|
from os.path import basename
|
||||||
|
from time import sleep
|
||||||
|
from typing import cast, List, Optional
|
||||||
import argparse
|
import argparse
|
||||||
|
import datetime as dt
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
import pipeclient
|
# PyQt imports
|
||||||
from pygame import mixer
|
|
||||||
from PyQt6.QtCore import (
|
from PyQt6.QtCore import (
|
||||||
pyqtSignal,
|
pyqtSignal,
|
||||||
QDate,
|
QDate,
|
||||||
@ -49,8 +44,14 @@ from PyQt6.QtWidgets import (
|
|||||||
QProgressBar,
|
QProgressBar,
|
||||||
QPushButton,
|
QPushButton,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Third party imports
|
||||||
|
from pygame import mixer
|
||||||
|
import pipeclient
|
||||||
|
from sqlalchemy.orm import scoped_session
|
||||||
import stackprinter # type: ignore
|
import stackprinter # type: ignore
|
||||||
|
|
||||||
|
# App imports
|
||||||
from classes import (
|
from classes import (
|
||||||
track_sequence,
|
track_sequence,
|
||||||
FadeCurve,
|
FadeCurve,
|
||||||
@ -58,23 +59,19 @@ from classes import (
|
|||||||
PlaylistTrack,
|
PlaylistTrack,
|
||||||
)
|
)
|
||||||
from config import Config
|
from config import Config
|
||||||
from dbconfig import (
|
from dbtables import db
|
||||||
engine,
|
|
||||||
scoped_session,
|
|
||||||
Session,
|
|
||||||
)
|
|
||||||
from dialogs import TrackSelectDialog
|
from dialogs import TrackSelectDialog
|
||||||
from log import log
|
from log import log
|
||||||
from models import Base, Carts, Playdates, PlaylistRows, Playlists, Settings, Tracks
|
from models import Carts, Playdates, PlaylistRows, Playlists, Settings, Tracks
|
||||||
from playlistmodel import PlaylistModel, PlaylistProxyModel
|
from playlistmodel import PlaylistModel, PlaylistProxyModel
|
||||||
from playlists import PlaylistTab
|
from playlists import PlaylistTab
|
||||||
|
from ui import icons_rc # noqa F401
|
||||||
from ui.dlg_cart_ui import Ui_DialogCartEdit # type: ignore
|
from ui.dlg_cart_ui import Ui_DialogCartEdit # type: ignore
|
||||||
from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore
|
from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore
|
||||||
from ui.downloadcsv_ui import Ui_DateSelect # type: ignore
|
from ui.downloadcsv_ui import Ui_DateSelect # type: ignore
|
||||||
from ui.main_window_ui import Ui_MainWindow # type: ignore
|
from ui.main_window_ui import Ui_MainWindow # type: ignore
|
||||||
from utilities import check_db, update_bitrates
|
from utilities import check_db, update_bitrates
|
||||||
import helpers
|
import helpers
|
||||||
from ui import icons_rc # noqa F401
|
|
||||||
import music
|
import music
|
||||||
|
|
||||||
|
|
||||||
@ -168,7 +165,7 @@ class ImportTrack(QObject):
|
|||||||
Create track objects from passed files and add to visible playlist
|
Create track objects from passed files and add to visible playlist
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
for fname in self.filenames:
|
for fname in self.filenames:
|
||||||
self.signals.status_message_signal.emit(
|
self.signals.status_message_signal.emit(
|
||||||
f"Importing {basename(fname)}", 5000
|
f"Importing {basename(fname)}", 5000
|
||||||
@ -255,7 +252,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
except subprocess.CalledProcessError as exc_info:
|
except subprocess.CalledProcessError as exc_info:
|
||||||
git_tag = str(exc_info.output)
|
git_tag = str(exc_info.output)
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
if session.bind:
|
if session.bind:
|
||||||
dbname = session.bind.engine.url.database
|
dbname = session.bind.engine.url.database
|
||||||
|
|
||||||
@ -317,7 +314,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
def cart_edit(self, btn: CartButton, event: QEvent):
|
def cart_edit(self, btn: CartButton, event: QEvent):
|
||||||
"""Handle context menu for cart button"""
|
"""Handle context menu for cart button"""
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
cart = session.query(Carts).get(btn.cart_id)
|
cart = session.query(Carts).get(btn.cart_id)
|
||||||
if cart is None:
|
if cart is None:
|
||||||
log.error("cart_edit: cart not found")
|
log.error("cart_edit: cart not found")
|
||||||
@ -349,7 +346,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
def carts_init(self) -> None:
|
def carts_init(self) -> None:
|
||||||
"""Initialse carts data structures"""
|
"""Initialse carts data structures"""
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
# Number carts from 1 for humanity
|
# Number carts from 1 for humanity
|
||||||
for cart_number in range(1, Config.CARTS_COUNT + 1):
|
for cart_number in range(1, Config.CARTS_COUNT + 1):
|
||||||
cart = session.query(Carts).get(cart_number)
|
cart = session.query(Carts).get(cart_number)
|
||||||
@ -426,7 +423,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self, "Track playing", "Can't close application while track is playing"
|
self, "Track playing", "Can't close application while track is playing"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
settings = Settings.all_as_dict(session)
|
settings = Settings.all_as_dict(session)
|
||||||
record = settings["mainwindow_height"]
|
record = settings["mainwindow_height"]
|
||||||
if record.f_int != self.height():
|
if record.f_int != self.height():
|
||||||
@ -495,7 +492,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Record playlist as closed and update remaining playlist tabs
|
# Record playlist as closed and update remaining playlist tabs
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
playlist = session.get(Playlists, closing_tab_playlist_id)
|
playlist = session.get(Playlists, closing_tab_playlist_id)
|
||||||
if playlist:
|
if playlist:
|
||||||
playlist.close()
|
playlist.close()
|
||||||
@ -588,7 +585,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
def create_and_show_playlist(self) -> None:
|
def create_and_show_playlist(self) -> None:
|
||||||
"""Create new playlist and display it"""
|
"""Create new playlist and display it"""
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
playlist = self.create_playlist(session)
|
playlist = self.create_playlist(session)
|
||||||
if playlist:
|
if playlist:
|
||||||
self.create_playlist_tab(playlist)
|
self.create_playlist_tab(playlist)
|
||||||
@ -636,7 +633,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
Delete current playlist
|
Delete current playlist
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
playlist_id = self.active_tab().playlist_id
|
playlist_id = self.active_tab().playlist_id
|
||||||
playlist = session.get(Playlists, playlist_id)
|
playlist = session.get(Playlists, playlist_id)
|
||||||
if playlist:
|
if playlist:
|
||||||
@ -670,7 +667,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
path += ".csv"
|
path += ".csv"
|
||||||
|
|
||||||
with open(path, "w") as f:
|
with open(path, "w") as f:
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
for playdate in Playdates.played_after(session, start_dt):
|
for playdate in Playdates.played_after(session, start_dt):
|
||||||
f.write(f"{playdate.track.artist},{playdate.track.title}\n")
|
f.write(f"{playdate.track.artist},{playdate.track.title}\n")
|
||||||
|
|
||||||
@ -702,7 +699,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
playlist_id = self.active_tab().playlist_id
|
playlist_id = self.active_tab().playlist_id
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
# Get output filename
|
# Get output filename
|
||||||
playlist = session.get(Playlists, playlist_id)
|
playlist = session.get(Playlists, playlist_id)
|
||||||
if not playlist:
|
if not playlist:
|
||||||
@ -784,7 +781,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
if not dlg.exec():
|
if not dlg.exec():
|
||||||
return
|
return
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
new_tracks = []
|
new_tracks = []
|
||||||
for fname in dlg.selectedFiles():
|
for fname in dlg.selectedFiles():
|
||||||
txt = ""
|
txt = ""
|
||||||
@ -883,7 +880,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.active_tab().source_model_selected_row_number()
|
self.active_tab().source_model_selected_row_number()
|
||||||
or self.active_proxy_model().rowCount()
|
or self.active_proxy_model().rowCount()
|
||||||
)
|
)
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
dlg = TrackSelectDialog(
|
dlg = TrackSelectDialog(
|
||||||
session=session,
|
session=session,
|
||||||
new_row_number=new_row_number,
|
new_row_number=new_row_number,
|
||||||
@ -895,7 +892,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
"""Load the playlists that were open when the last session closed"""
|
"""Load the playlists that were open when the last session closed"""
|
||||||
|
|
||||||
playlist_ids = []
|
playlist_ids = []
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
for playlist in Playlists.get_open(session):
|
for playlist in Playlists.get_open(session):
|
||||||
if playlist:
|
if playlist:
|
||||||
_ = self.create_playlist_tab(playlist)
|
_ = self.create_playlist_tab(playlist)
|
||||||
@ -952,7 +949,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
visible_tab = self.active_tab()
|
visible_tab = self.active_tab()
|
||||||
source_playlist_id = visible_tab.playlist_id
|
source_playlist_id = visible_tab.playlist_id
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
for playlist in Playlists.get_all(session):
|
for playlist in Playlists.get_all(session):
|
||||||
if playlist.id == source_playlist_id:
|
if playlist.id == source_playlist_id:
|
||||||
continue
|
continue
|
||||||
@ -1005,7 +1002,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
def new_from_template(self) -> None:
|
def new_from_template(self) -> None:
|
||||||
"""Create new playlist from template"""
|
"""Create new playlist from template"""
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
templates = Playlists.get_all_templates(session)
|
templates = Playlists.get_all_templates(session)
|
||||||
dlg = SelectPlaylistDialog(self, playlists=templates, session=session)
|
dlg = SelectPlaylistDialog(self, playlists=templates, session=session)
|
||||||
dlg.exec()
|
dlg.exec()
|
||||||
@ -1031,7 +1028,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
def open_playlist(self) -> None:
|
def open_playlist(self) -> None:
|
||||||
"""Open existing playlist"""
|
"""Open existing playlist"""
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
playlists = Playlists.get_closed(session)
|
playlists = Playlists.get_closed(session)
|
||||||
dlg = SelectPlaylistDialog(self, playlists=playlists, session=session)
|
dlg = SelectPlaylistDialog(self, playlists=playlists, session=session)
|
||||||
dlg.exec()
|
dlg.exec()
|
||||||
@ -1193,7 +1190,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
Rename current playlist
|
Rename current playlist
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
playlist_id = self.active_tab().playlist_id
|
playlist_id = self.active_tab().playlist_id
|
||||||
playlist = session.get(Playlists, playlist_id)
|
playlist = session.get(Playlists, playlist_id)
|
||||||
if playlist:
|
if playlist:
|
||||||
@ -1242,7 +1239,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
def save_as_template(self) -> None:
|
def save_as_template(self) -> None:
|
||||||
"""Save current playlist as template"""
|
"""Save current playlist as template"""
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
template_names = [a.name for a in Playlists.get_all_templates(session)]
|
template_names = [a.name for a in Playlists.get_all_templates(session)]
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@ -1304,7 +1301,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
def set_main_window_size(self) -> None:
|
def set_main_window_size(self) -> None:
|
||||||
"""Set size of window from database"""
|
"""Set size of window from database"""
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
settings = Settings.all_as_dict(session)
|
settings = Settings.all_as_dict(session)
|
||||||
record = settings["mainwindow_x"]
|
record = settings["mainwindow_x"]
|
||||||
x = record.f_int or 1
|
x = record.f_int or 1
|
||||||
@ -1751,18 +1748,15 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
# Run as required
|
# Run as required
|
||||||
if args.check_db:
|
if args.check_db:
|
||||||
log.debug("Updating database")
|
log.debug("Checking database")
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
check_db(session)
|
check_db(session)
|
||||||
engine.dispose()
|
|
||||||
elif args.update_bitrates:
|
elif args.update_bitrates:
|
||||||
log.debug("Update bitrates")
|
log.debug("Update bitrates")
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
update_bitrates(session)
|
update_bitrates(session)
|
||||||
engine.dispose()
|
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
Base.metadata.create_all(engine)
|
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
# PyQt6 defaults to a grey for labels
|
# PyQt6 defaults to a grey for labels
|
||||||
palette = app.palette()
|
palette = app.palette()
|
||||||
@ -1780,7 +1774,6 @@ if __name__ == "__main__":
|
|||||||
win = Window()
|
win = Window()
|
||||||
win.show()
|
win.show()
|
||||||
status = app.exec()
|
status = app.exec()
|
||||||
engine.dispose()
|
|
||||||
sys.exit(status)
|
sys.exit(status)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
if os.environ["MM_ENV"] == "PRODUCTION":
|
if os.environ["MM_ENV"] == "PRODUCTION":
|
||||||
|
|||||||
@ -1,5 +1,14 @@
|
|||||||
|
# Standard library imports
|
||||||
# Allow forward reference to PlaylistModel
|
# Allow forward reference to PlaylistModel
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
# PyQt imports
|
||||||
|
|
||||||
|
# Third party imports
|
||||||
|
|
||||||
|
# App imports
|
||||||
|
from dbtables import db
|
||||||
|
|
||||||
import obsws_python as obs # type: ignore
|
import obsws_python as obs # type: ignore
|
||||||
import re
|
import re
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
@ -25,7 +34,6 @@ from PyQt6.QtGui import (
|
|||||||
|
|
||||||
from classes import track_sequence, MusicMusterSignals, PlaylistTrack
|
from classes import track_sequence, MusicMusterSignals, PlaylistTrack
|
||||||
from config import Config
|
from config import Config
|
||||||
from dbconfig import scoped_session, Session
|
|
||||||
from helpers import (
|
from helpers import (
|
||||||
file_is_unreadable,
|
file_is_unreadable,
|
||||||
get_embedded_time,
|
get_embedded_time,
|
||||||
@ -129,7 +137,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
self.signals.end_reset_model_signal.connect(self.end_reset_model)
|
self.signals.end_reset_model_signal.connect(self.end_reset_model)
|
||||||
self.signals.row_order_changed_signal.connect(self.row_order_changed)
|
self.signals.row_order_changed_signal.connect(self.row_order_changed)
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
# Ensure row numbers in playlist are contiguous
|
# Ensure row numbers in playlist are contiguous
|
||||||
PlaylistRows.fixup_rownumbers(session, playlist_id)
|
PlaylistRows.fixup_rownumbers(session, playlist_id)
|
||||||
# Populate self.playlist_rows
|
# Populate self.playlist_rows
|
||||||
@ -165,7 +173,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
"Header row already has track associated"
|
"Header row already has track associated"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
plr = session.get(PlaylistRows, prd.plrid)
|
plr = session.get(PlaylistRows, prd.plrid)
|
||||||
if plr:
|
if plr:
|
||||||
# Add track to PlaylistRows
|
# Add track to PlaylistRows
|
||||||
@ -187,7 +195,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
# Header row
|
# Header row
|
||||||
if self.is_header_row(row):
|
if self.is_header_row(row):
|
||||||
# Check for specific header colouring
|
# Check for specific header colouring
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
note_colour = NoteColours.get_colour(session, prd.note)
|
note_colour = NoteColours.get_colour(session, prd.note)
|
||||||
if note_colour:
|
if note_colour:
|
||||||
return QBrush(QColor(note_colour))
|
return QBrush(QColor(note_colour))
|
||||||
@ -216,7 +224,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
return QBrush(QColor(Config.COLOUR_BITRATE_OK))
|
return QBrush(QColor(Config.COLOUR_BITRATE_OK))
|
||||||
if column == Col.NOTE.value:
|
if column == Col.NOTE.value:
|
||||||
if prd.note:
|
if prd.note:
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
note_colour = NoteColours.get_colour(session, prd.note)
|
note_colour = NoteColours.get_colour(session, prd.note)
|
||||||
if note_colour:
|
if note_colour:
|
||||||
return QBrush(QColor(note_colour))
|
return QBrush(QColor(note_colour))
|
||||||
@ -275,7 +283,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
log.debug("Call OBS scene change")
|
log.debug("Call OBS scene change")
|
||||||
self.obs_scene_change(row_number)
|
self.obs_scene_change(row_number)
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
# Update Playdates in database
|
# Update Playdates in database
|
||||||
log.debug("update playdates")
|
log.debug("update playdates")
|
||||||
Playdates(session, track_sequence.now.track_id)
|
Playdates(session, track_sequence.now.track_id)
|
||||||
@ -377,7 +385,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
Delete from highest row back so that not yet deleted row numbers don't change.
|
Delete from highest row back so that not yet deleted row numbers don't change.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
for row_number in sorted(row_numbers, reverse=True):
|
for row_number in sorted(row_numbers, reverse=True):
|
||||||
log.info(f"delete_rows(), {row_number=}")
|
log.info(f"delete_rows(), {row_number=}")
|
||||||
super().beginRemoveRows(QModelIndex(), row_number, row_number)
|
super().beginRemoveRows(QModelIndex(), row_number, row_number)
|
||||||
@ -454,7 +462,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
if playlist_id != self.playlist_id:
|
if playlist_id != self.playlist_id:
|
||||||
log.debug(f"end_reset_model: not us ({self.playlist_id=})")
|
log.debug(f"end_reset_model: not us ({self.playlist_id=})")
|
||||||
return
|
return
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
self.refresh_data(session)
|
self.refresh_data(session)
|
||||||
super().endResetModel()
|
super().endResetModel()
|
||||||
self.reset_track_sequence_row_numbers()
|
self.reset_track_sequence_row_numbers()
|
||||||
@ -754,7 +762,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
new_row_number = self._get_new_row_number(proposed_row_number)
|
new_row_number = self._get_new_row_number(proposed_row_number)
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
super().beginInsertRows(QModelIndex(), new_row_number, new_row_number)
|
super().beginInsertRows(QModelIndex(), new_row_number, new_row_number)
|
||||||
plr = PlaylistRows.insert_row(session, self.playlist_id, new_row_number)
|
plr = PlaylistRows.insert_row(session, self.playlist_id, new_row_number)
|
||||||
|
|
||||||
@ -820,7 +828,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
Mark row as unplayed
|
Mark row as unplayed
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
for row_number in row_numbers:
|
for row_number in row_numbers:
|
||||||
plr = session.get(PlaylistRows, self.playlist_rows[row_number].plrid)
|
plr = session.get(PlaylistRows, self.playlist_rows[row_number].plrid)
|
||||||
if not plr:
|
if not plr:
|
||||||
@ -883,7 +891,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
plrid = self.playlist_rows[oldrow].plrid
|
plrid = self.playlist_rows[oldrow].plrid
|
||||||
sqla_map.append({"plrid": plrid, "plr_rownum": newrow})
|
sqla_map.append({"plrid": plrid, "plr_rownum": newrow})
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
PlaylistRows.update_plr_rownumbers(session, self.playlist_id, sqla_map)
|
PlaylistRows.update_plr_rownumbers(session, self.playlist_id, sqla_map)
|
||||||
# Update playlist_rows
|
# Update playlist_rows
|
||||||
self.refresh_data(session)
|
self.refresh_data(session)
|
||||||
@ -912,7 +920,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
# Prepare destination playlist for a reset
|
# Prepare destination playlist for a reset
|
||||||
self.signals.begin_reset_model_signal.emit(to_playlist_id)
|
self.signals.begin_reset_model_signal.emit(to_playlist_id)
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
# Make room in destination playlist
|
# Make room in destination playlist
|
||||||
max_destination_row_number = PlaylistRows.get_last_used_row(
|
max_destination_row_number = PlaylistRows.get_last_used_row(
|
||||||
session, to_playlist_id
|
session, to_playlist_id
|
||||||
@ -962,7 +970,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
log.info(f"move_track_add_note({new_row_number=}, {existing_prd=}, {note=}")
|
log.info(f"move_track_add_note({new_row_number=}, {existing_prd=}, {note=}")
|
||||||
|
|
||||||
if note:
|
if note:
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
plr = session.get(PlaylistRows, existing_prd.plrid)
|
plr = session.get(PlaylistRows, existing_prd.plrid)
|
||||||
if plr:
|
if plr:
|
||||||
if plr.note:
|
if plr.note:
|
||||||
@ -1050,7 +1058,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
# Update display
|
# Update display
|
||||||
self.invalidate_row(track_sequence.previous.plr_rownum)
|
self.invalidate_row(track_sequence.previous.plr_rownum)
|
||||||
|
|
||||||
def refresh_data(self, session: scoped_session):
|
def refresh_data(self, session: db.session):
|
||||||
"""Populate dicts for data calls"""
|
"""Populate dicts for data calls"""
|
||||||
|
|
||||||
# Populate self.playlist_rows with playlist data
|
# Populate self.playlist_rows with playlist data
|
||||||
@ -1071,7 +1079,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
log.info(f"remove_track({row_number=})")
|
log.info(f"remove_track({row_number=})")
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
plr = session.get(PlaylistRows, self.playlist_rows[row_number].plrid)
|
plr = session.get(PlaylistRows, self.playlist_rows[row_number].plrid)
|
||||||
if plr:
|
if plr:
|
||||||
plr.track_id = None
|
plr.track_id = None
|
||||||
@ -1085,7 +1093,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
track_id = self.playlist_rows[row_number].track_id
|
track_id = self.playlist_rows[row_number].track_id
|
||||||
if track_id:
|
if track_id:
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
track = session.get(Tracks, track_id)
|
track = session.get(Tracks, track_id)
|
||||||
set_track_metadata(track)
|
set_track_metadata(track)
|
||||||
self.refresh_row(session, row_number)
|
self.refresh_row(session, row_number)
|
||||||
@ -1101,7 +1109,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
# Check the track_sequence next, now and previous plrs and
|
# Check the track_sequence next, now and previous plrs and
|
||||||
# update the row number
|
# update the row number
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
if track_sequence.next.plr_rownum:
|
if track_sequence.next.plr_rownum:
|
||||||
next_plr = session.get(PlaylistRows, track_sequence.next.plr_id)
|
next_plr = session.get(PlaylistRows, track_sequence.next.plr_id)
|
||||||
if next_plr:
|
if next_plr:
|
||||||
@ -1206,7 +1214,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Update track_sequence
|
# Update track_sequence
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
track_sequence.next = PlaylistTrack()
|
track_sequence.next = PlaylistTrack()
|
||||||
try:
|
try:
|
||||||
plrid = self.playlist_rows[row_number].plrid
|
plrid = self.playlist_rows[row_number].plrid
|
||||||
@ -1246,7 +1254,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
row_number = index.row()
|
row_number = index.row()
|
||||||
column = index.column()
|
column = index.column()
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
plr = session.get(PlaylistRows, self.playlist_rows[row_number].plrid)
|
plr = session.get(PlaylistRows, self.playlist_rows[row_number].plrid)
|
||||||
if not plr:
|
if not plr:
|
||||||
print(
|
print(
|
||||||
@ -1455,7 +1463,7 @@ class PlaylistProxyModel(QSortFilterProxyModel):
|
|||||||
if self.source_model.played_tracks_hidden:
|
if self.source_model.played_tracks_hidden:
|
||||||
if self.source_model.is_played_row(source_row):
|
if self.source_model.is_played_row(source_row):
|
||||||
# Don't hide current or next track
|
# Don't hide current or next track
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
if track_sequence.next.plr_id:
|
if track_sequence.next.plr_id:
|
||||||
next_plr = session.get(PlaylistRows, track_sequence.next.plr_id)
|
next_plr = session.get(PlaylistRows, track_sequence.next.plr_id)
|
||||||
if (
|
if (
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
|
# Standard library imports
|
||||||
|
from typing import Callable, cast, List, Optional, TYPE_CHECKING
|
||||||
import psutil
|
import psutil
|
||||||
import time
|
import time
|
||||||
from pprint import pprint
|
|
||||||
from typing import Callable, cast, List, Optional, TYPE_CHECKING
|
|
||||||
|
|
||||||
|
# PyQt imports
|
||||||
from PyQt6.QtCore import (
|
from PyQt6.QtCore import (
|
||||||
QEvent,
|
QEvent,
|
||||||
QModelIndex,
|
QModelIndex,
|
||||||
@ -30,10 +31,13 @@ from PyQt6.QtWidgets import (
|
|||||||
QStyleOption,
|
QStyleOption,
|
||||||
)
|
)
|
||||||
|
|
||||||
from dbconfig import Session
|
# Third party imports
|
||||||
from dialogs import TrackSelectDialog
|
|
||||||
|
# App imports
|
||||||
from classes import MusicMusterSignals, track_sequence
|
from classes import MusicMusterSignals, track_sequence
|
||||||
from config import Config
|
from config import Config
|
||||||
|
from dbtables import db
|
||||||
|
from dialogs import TrackSelectDialog
|
||||||
from helpers import (
|
from helpers import (
|
||||||
ask_yes_no,
|
ask_yes_no,
|
||||||
ms_to_mmss,
|
ms_to_mmss,
|
||||||
@ -42,10 +46,9 @@ from helpers import (
|
|||||||
)
|
)
|
||||||
from log import log
|
from log import log
|
||||||
from models import Settings
|
from models import Settings
|
||||||
|
from playlistmodel import PlaylistModel, PlaylistProxyModel
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from musicmuster import Window
|
from musicmuster import Window
|
||||||
from playlistmodel import PlaylistModel, PlaylistProxyModel
|
|
||||||
|
|
||||||
|
|
||||||
class EscapeDelegate(QStyledItemDelegate):
|
class EscapeDelegate(QStyledItemDelegate):
|
||||||
@ -335,7 +338,7 @@ class PlaylistTab(QTableView):
|
|||||||
if model_row_number is None:
|
if model_row_number is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
dlg = TrackSelectDialog(
|
dlg = TrackSelectDialog(
|
||||||
session=session,
|
session=session,
|
||||||
new_row_number=model_row_number,
|
new_row_number=model_row_number,
|
||||||
@ -536,7 +539,7 @@ class PlaylistTab(QTableView):
|
|||||||
# Resize rows if necessary
|
# Resize rows if necessary
|
||||||
self.resizeRowsToContents()
|
self.resizeRowsToContents()
|
||||||
|
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
attr_name = f"playlist_col_{column_number}_width"
|
attr_name = f"playlist_col_{column_number}_width"
|
||||||
record = Settings.get_int_settings(session, attr_name)
|
record = Settings.get_int_settings(session, attr_name)
|
||||||
record.f_int = self.columnWidth(column_number)
|
record.f_int = self.columnWidth(column_number)
|
||||||
@ -830,7 +833,7 @@ class PlaylistTab(QTableView):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Last column is set to stretch so ignore it here
|
# Last column is set to stretch so ignore it here
|
||||||
with Session() as session:
|
with db.Session() as session:
|
||||||
for column_number in range(header.count() - 1):
|
for column_number in range(header.count() - 1):
|
||||||
attr_name = f"playlist_col_{column_number}_width"
|
attr_name = f"playlist_col_{column_number}_width"
|
||||||
record = Settings.get_int_settings(session, attr_name)
|
record = Settings.get_int_settings(session, attr_name)
|
||||||
|
|||||||
20
poetry.lock
generated
20
poetry.lock
generated
@ -12,6 +12,24 @@ files = [
|
|||||||
{file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"},
|
{file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alchemical"
|
||||||
|
version = "1.0.1"
|
||||||
|
description = "Modern SQLAlchemy simplified"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "alchemical-1.0.1-py3-none-any.whl", hash = "sha256:79a98d459ed4f8afa8ed44b21d4d2ccf3586c76d73f53f647a9338aaba2bb33c"},
|
||||||
|
{file = "alchemical-1.0.1.tar.gz", hash = "sha256:cff882cfef9533a56c53aa6bb38d5fa19939ea283226017c7c9369b73422200a"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
sqlalchemy = ">=1.4.24"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["sphinx"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alembic"
|
name = "alembic"
|
||||||
version = "1.13.1"
|
version = "1.13.1"
|
||||||
@ -2273,4 +2291,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.9"
|
python-versions = "^3.9"
|
||||||
content-hash = "500cefc31e30cba9ae917cc51b7407961d69825d1fcae53515ed1fa12f4ab171"
|
content-hash = "f4fb2696ae984283c4c0d7816ba7cbd7be714695d6eb3c84b5da62b3809f9c82"
|
||||||
|
|||||||
@ -27,6 +27,7 @@ pyqt6-webengine = "^6.5.0"
|
|||||||
pygame = "^2.4.0"
|
pygame = "^2.4.0"
|
||||||
pyqtgraph = "^0.13.3"
|
pyqtgraph = "^0.13.3"
|
||||||
colorlog = "^6.8.0"
|
colorlog = "^6.8.0"
|
||||||
|
alchemical = "^1.0.1"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
ipdb = "^0.13.9"
|
ipdb = "^0.13.9"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user