Compare commits
3 Commits
0d4b306fc4
...
6a2bcfff19
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a2bcfff19 | ||
|
|
eb7ed1d6dd | ||
|
|
78a9103490 |
16
app/log.py
16
app/log.py
@ -76,3 +76,19 @@ syslog.setFormatter(syslog_formatter)
|
|||||||
# add the handlers to the log
|
# add the handlers to the log
|
||||||
log.addHandler(stderr)
|
log.addHandler(stderr)
|
||||||
log.addHandler(syslog)
|
log.addHandler(syslog)
|
||||||
|
|
||||||
|
|
||||||
|
def log_uncaught_exceptions(ex_cls, ex, tb):
|
||||||
|
|
||||||
|
from helpers import send_mail
|
||||||
|
|
||||||
|
print("\033[1;31;47m")
|
||||||
|
logging.critical(''.join(traceback.format_tb(tb)))
|
||||||
|
print("\033[1;37;40m")
|
||||||
|
stackprinter.show(style="lightbg")
|
||||||
|
msg = stackprinter.format(ex)
|
||||||
|
send_mail(Config.ERRORS_TO, Config.ERRORS_FROM,
|
||||||
|
"Exception from musicmuster", msg)
|
||||||
|
|
||||||
|
|
||||||
|
sys.excepthook = log_uncaught_exceptions
|
||||||
|
|||||||
@ -251,7 +251,7 @@ class Playlists(Base):
|
|||||||
id: int = Column(Integer, primary_key=True, autoincrement=True)
|
id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
name: str = Column(String(32), nullable=False, unique=True)
|
name: str = Column(String(32), nullable=False, unique=True)
|
||||||
last_used = Column(DateTime, default=None, nullable=True)
|
last_used = Column(DateTime, default=None, nullable=True)
|
||||||
loaded = Column(Boolean, default=True, nullable=False)
|
tab = Column(Integer, default=False, nullable=True, unique=True)
|
||||||
is_template = Column(Boolean, default=False, nullable=False)
|
is_template = Column(Boolean, default=False, nullable=False)
|
||||||
rows = relationship(
|
rows = relationship(
|
||||||
"PlaylistRows",
|
"PlaylistRows",
|
||||||
@ -287,7 +287,7 @@ class Playlists(Base):
|
|||||||
def close(self, session: Session) -> None:
|
def close(self, session: Session) -> None:
|
||||||
"""Mark playlist as unloaded"""
|
"""Mark playlist as unloaded"""
|
||||||
|
|
||||||
self.loaded = False
|
self.tab = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_playlist_from_template(cls,
|
def create_playlist_from_template(cls,
|
||||||
@ -310,7 +310,7 @@ class Playlists(Base):
|
|||||||
session.execute(
|
session.execute(
|
||||||
select(cls)
|
select(cls)
|
||||||
.filter(cls.is_template.is_(False))
|
.filter(cls.is_template.is_(False))
|
||||||
.order_by(cls.loaded.desc(), cls.last_used.desc())
|
.order_by(cls.tab.desc(), cls.last_used.desc())
|
||||||
)
|
)
|
||||||
.scalars()
|
.scalars()
|
||||||
.all()
|
.all()
|
||||||
@ -338,7 +338,7 @@ class Playlists(Base):
|
|||||||
session.execute(
|
session.execute(
|
||||||
select(cls)
|
select(cls)
|
||||||
.filter(
|
.filter(
|
||||||
cls.loaded.is_(False),
|
cls.tab.is_(None),
|
||||||
cls.is_template.is_(False)
|
cls.is_template.is_(False)
|
||||||
)
|
)
|
||||||
.order_by(cls.last_used.desc())
|
.order_by(cls.last_used.desc())
|
||||||
@ -350,25 +350,45 @@ class Playlists(Base):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_open(cls, session: Session) -> List[Optional["Playlists"]]:
|
def get_open(cls, session: Session) -> List[Optional["Playlists"]]:
|
||||||
"""
|
"""
|
||||||
Return a list of playlists marked "loaded", ordered by loaded date.
|
Return a list of loaded playlists ordered by tab order.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return (
|
return (
|
||||||
session.execute(
|
session.execute(
|
||||||
select(cls)
|
select(cls)
|
||||||
.where(cls.loaded.is_(True))
|
.where(cls.tab.is_not(None))
|
||||||
.order_by(cls.last_used.desc())
|
.order_by(cls.tab)
|
||||||
)
|
)
|
||||||
.scalars()
|
.scalars()
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
def mark_open(self, session: Session) -> None:
|
def mark_open(self, session: Session, tab_index: int) -> None:
|
||||||
"""Mark playlist as loaded and used now"""
|
"""Mark playlist as loaded and used now"""
|
||||||
|
|
||||||
self.loaded = True
|
self.tab = tab_index
|
||||||
self.last_used = datetime.now()
|
self.last_used = datetime.now()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def move_tab(session: Session, frm: int, to: int) -> None:
|
||||||
|
"""Move tabs"""
|
||||||
|
|
||||||
|
row_frm = session.execute(
|
||||||
|
select(Playlists)
|
||||||
|
.filter_by(tab=frm)
|
||||||
|
).scalar_one()
|
||||||
|
|
||||||
|
row_to = session.execute(
|
||||||
|
select(Playlists)
|
||||||
|
.filter_by(tab=to)
|
||||||
|
).scalar_one()
|
||||||
|
|
||||||
|
row_frm.tab = None
|
||||||
|
row_to.tab = None
|
||||||
|
session.commit()
|
||||||
|
row_to.tab = frm
|
||||||
|
row_frm.tab = to
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def save_as_template(session: Session,
|
def save_as_template(session: Session,
|
||||||
playlist_id: int, template_name: str) -> None:
|
playlist_id: int, template_name: str) -> None:
|
||||||
|
|||||||
@ -343,6 +343,11 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
if record.f_int != splitter_bottom:
|
if record.f_int != splitter_bottom:
|
||||||
record.update(session, {'f_int': splitter_bottom})
|
record.update(session, {'f_int': splitter_bottom})
|
||||||
|
|
||||||
|
# Save current tab
|
||||||
|
record = Settings.get_int_settings(session, "active_tab")
|
||||||
|
record.update(session,
|
||||||
|
{'f_int': self.tabPlaylist.currentIndex()})
|
||||||
|
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|
||||||
def close_playlist_tab(self) -> None:
|
def close_playlist_tab(self) -> None:
|
||||||
@ -419,6 +424,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.tabPlaylist.currentChanged.connect(
|
self.tabPlaylist.currentChanged.connect(
|
||||||
lambda: self.tabPlaylist.currentWidget().tab_visible())
|
lambda: self.tabPlaylist.currentWidget().tab_visible())
|
||||||
self.tabPlaylist.tabCloseRequested.connect(self.close_tab)
|
self.tabPlaylist.tabCloseRequested.connect(self.close_tab)
|
||||||
|
self.tabBar = self.tabPlaylist.tabBar()
|
||||||
|
self.tabBar.tabMoved.connect(self.move_tab)
|
||||||
self.txtSearch.returnPressed.connect(self.search_playlist_return)
|
self.txtSearch.returnPressed.connect(self.search_playlist_return)
|
||||||
|
|
||||||
self.timer.timeout.connect(self.tick)
|
self.timer.timeout.connect(self.tick)
|
||||||
@ -442,10 +449,10 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.create_playlist_tab(session, playlist)
|
self.create_playlist_tab(session, playlist)
|
||||||
|
|
||||||
def create_playlist_tab(self, session: Session,
|
def create_playlist_tab(self, session: Session,
|
||||||
playlist: Playlists) -> None:
|
playlist: Playlists) -> int:
|
||||||
"""
|
"""
|
||||||
Take the passed playlist database object, create a playlist tab and
|
Take the passed playlist database object, create a playlist tab and
|
||||||
add tab to display.
|
add tab to display. Return index number of tab.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
playlist_tab = PlaylistTab(
|
playlist_tab = PlaylistTab(
|
||||||
@ -453,6 +460,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
idx = self.tabPlaylist.addTab(playlist_tab, playlist.name)
|
idx = self.tabPlaylist.addTab(playlist_tab, playlist.name)
|
||||||
self.tabPlaylist.setCurrentIndex(idx)
|
self.tabPlaylist.setCurrentIndex(idx)
|
||||||
|
|
||||||
|
return idx
|
||||||
|
|
||||||
def cut_rows(self) -> None:
|
def cut_rows(self) -> None:
|
||||||
"""
|
"""
|
||||||
Cut rows ready for pasting.
|
Cut rows ready for pasting.
|
||||||
@ -759,8 +768,11 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
for playlist in Playlists.get_open(session):
|
for playlist in Playlists.get_open(session):
|
||||||
self.create_playlist_tab(session, playlist)
|
_ = self.create_playlist_tab(session, playlist)
|
||||||
playlist.mark_open(session)
|
# Set active tab
|
||||||
|
record = Settings.get_int_settings(session, "active_tab")
|
||||||
|
if record and record.f_int is not None:
|
||||||
|
self.tabPlaylist.setCurrentIndex(record.f_int)
|
||||||
|
|
||||||
def move_playlist_rows(self, session: Session,
|
def move_playlist_rows(self, session: Session,
|
||||||
playlistrows: List[PlaylistRows]) -> None:
|
playlistrows: List[PlaylistRows]) -> None:
|
||||||
@ -832,6 +844,12 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.visible_playlist_tab().get_selected_playlistrows(session)
|
self.visible_playlist_tab().get_selected_playlistrows(session)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def move_tab(self, frm: int, to: int) -> None:
|
||||||
|
"""Handle tabs being moved"""
|
||||||
|
|
||||||
|
with Session() as session:
|
||||||
|
Playlists.move_tab(session, frm, to)
|
||||||
|
|
||||||
def move_unplayed(self) -> None:
|
def move_unplayed(self) -> None:
|
||||||
"""
|
"""
|
||||||
Move unplayed rows to another playlist
|
Move unplayed rows to another playlist
|
||||||
@ -862,8 +880,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
return
|
return
|
||||||
playlist = Playlists.create_playlist_from_template(
|
playlist = Playlists.create_playlist_from_template(
|
||||||
session, template, playlist_name)
|
session, template, playlist_name)
|
||||||
playlist.mark_open(session)
|
tab_index = self.create_playlist_tab(session, playlist)
|
||||||
self.create_playlist_tab(session, playlist)
|
playlist.mark_open(session, tab_index)
|
||||||
|
|
||||||
def open_playlist(self):
|
def open_playlist(self):
|
||||||
"""Open existing playlist"""
|
"""Open existing playlist"""
|
||||||
@ -875,8 +893,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
dlg.exec()
|
dlg.exec()
|
||||||
playlist = dlg.playlist
|
playlist = dlg.playlist
|
||||||
if playlist:
|
if playlist:
|
||||||
playlist.mark_open(session)
|
tab_index = self.create_playlist_tab(session, playlist)
|
||||||
self.create_playlist_tab(session, playlist)
|
playlist.mark_open(session, tab_index)
|
||||||
|
|
||||||
def paste_rows(self) -> None:
|
def paste_rows(self) -> None:
|
||||||
"""
|
"""
|
||||||
@ -914,8 +932,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
plr.playlist_id = dst_playlist_id
|
plr.playlist_id = dst_playlist_id
|
||||||
plr.row_number = row
|
plr.row_number = row
|
||||||
row += 1
|
row += 1
|
||||||
# Need to commit each row individually else only one row
|
|
||||||
# gets updated (don't know why)
|
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
@ -1645,7 +1661,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
msg = stackprinter.format(exc)
|
msg = stackprinter.format(exc)
|
||||||
send_mail(Config.ERRORS_TO, Config.ERRORS_FROM,
|
send_mail(Config.ERRORS_TO, Config.ERRORS_FROM,
|
||||||
"Exception from musicmuster", msg)
|
"Exception from musicmuster", msg)
|
||||||
|
|
||||||
print("\033[1;31;47mUnhandled exception starts\033[1;37;40m")
|
print("\033[1;31;47mUnhandled exception starts\033[1;37;40m")
|
||||||
stackprinter.show(style="darkbg2")
|
stackprinter.show(style="darkbg2")
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
"""Record tab number for open playlists
|
||||||
|
|
||||||
|
Revision ID: 4a7b4ab3354f
|
||||||
|
Revises: 6730f03317df
|
||||||
|
Create Date: 2022-12-20 15:38:28.318280
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '4a7b4ab3354f'
|
||||||
|
down_revision = '6730f03317df'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('playlists', sa.Column('tab', sa.Integer(), nullable=True))
|
||||||
|
op.create_unique_constraint(None, 'playlists', ['tab'])
|
||||||
|
op.drop_column('playlists', 'loaded')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('playlists', sa.Column('loaded', mysql.TINYINT(display_width=1), autoincrement=False, nullable=False))
|
||||||
|
op.drop_constraint(None, 'playlists', type_='unique')
|
||||||
|
op.drop_column('playlists', 'tab')
|
||||||
|
# ### end Alembic commands ###
|
||||||
21
poetry.lock
generated
21
poetry.lock
generated
@ -182,6 +182,24 @@ parso = ">=0.8.0,<0.9.0"
|
|||||||
qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
|
qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
|
||||||
testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"]
|
testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "line-profiler"
|
||||||
|
version = "4.0.2"
|
||||||
|
description = "Line-by-line profiler"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
all = ["pytest", "pytest-cov", "coverage", "ubelt", "cython", "scikit-build", "cmake", "ninja", "cibuildwheel", "cibuildwheel", "cibuildwheel", "cibuildwheel", "cibuildwheel", "cibuildwheel", "ipython", "ipython"]
|
||||||
|
all-strict = ["pytest (==4.6.11)", "pytest-cov (==2.10.1)", "coverage[toml] (==5.3)", "ubelt (==1.0.1)", "Cython (==3.0.0a11)", "scikit-build (==0.11.1)", "cmake (==3.21.2)", "ninja (==1.10.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.8.1)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "IPython (==0.13)", "IPython (==0.13)"]
|
||||||
|
build = ["cython", "scikit-build", "cmake", "ninja", "cibuildwheel", "cibuildwheel", "cibuildwheel", "cibuildwheel", "cibuildwheel", "cibuildwheel"]
|
||||||
|
build-strict = ["Cython (==3.0.0a11)", "scikit-build (==0.11.1)", "cmake (==3.21.2)", "ninja (==1.10.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.8.1)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)"]
|
||||||
|
ipython-strict = ["IPython (==0.13)", "IPython (==0.13)"]
|
||||||
|
ipython = ["ipython", "ipython"]
|
||||||
|
tests = ["pytest", "pytest-cov", "coverage", "ubelt", "ipython", "ipython"]
|
||||||
|
tests-strict = ["pytest (==4.6.11)", "pytest-cov (==2.10.1)", "coverage[toml] (==5.3)", "ubelt (==1.0.1)", "IPython (==0.13)", "IPython (==0.13)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mako"
|
name = "mako"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@ -716,7 +734,7 @@ python-versions = "*"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.9"
|
python-versions = "^3.9"
|
||||||
content-hash = "0fdda77377246e18b5e85459fa2c26173f14467f32e71c576b30fa0899ced8b0"
|
content-hash = "8a7dd5f873d901ffbe422d010464bcc8bb2acfa79329a95e4f18f213e120b5a7"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
alembic = [
|
alembic = [
|
||||||
@ -828,6 +846,7 @@ jedi = [
|
|||||||
{file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"},
|
{file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"},
|
||||||
{file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"},
|
{file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"},
|
||||||
]
|
]
|
||||||
|
line-profiler = []
|
||||||
mako = [
|
mako = [
|
||||||
{file = "Mako-1.2.0-py3-none-any.whl", hash = "sha256:23aab11fdbbb0f1051b93793a58323ff937e98e34aece1c4219675122e57e4ba"},
|
{file = "Mako-1.2.0-py3-none-any.whl", hash = "sha256:23aab11fdbbb0f1051b93793a58323ff937e98e34aece1c4219675122e57e4ba"},
|
||||||
{file = "Mako-1.2.0.tar.gz", hash = "sha256:9a7c7e922b87db3686210cf49d5d767033a41d4010b284e747682c92bddd8b39"},
|
{file = "Mako-1.2.0.tar.gz", hash = "sha256:9a7c7e922b87db3686210cf49d5d767033a41d4010b284e747682c92bddd8b39"},
|
||||||
|
|||||||
@ -33,6 +33,7 @@ mypy = "^0.931"
|
|||||||
pytest = "^7.0.1"
|
pytest = "^7.0.1"
|
||||||
pytest-qt = "^4.0.2"
|
pytest-qt = "^4.0.2"
|
||||||
pydub-stubs = "^0.25.1"
|
pydub-stubs = "^0.25.1"
|
||||||
|
line-profiler = "^4.0.2"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.0.0"]
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user