Optionally remove header colour directives from header

This commit is contained in:
Keith Edmunds 2024-12-14 14:49:07 +00:00
parent f7f4cdc622
commit 0391eed88e
5 changed files with 144 additions and 66 deletions

View File

@ -34,6 +34,7 @@ class NoteColoursTable(Model):
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)
strip_substring: Mapped[bool] = mapped_column(default=True, index=False)
def __repr__(self) -> str:
return (

View File

@ -333,6 +333,32 @@ 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

View File

@ -22,9 +22,9 @@ from sqlalchemy.orm import joinedload
from sqlalchemy.orm.session import Session
# App imports
from config import Config
from dbmanager import DatabaseManager
import dbtables
from config import Config
from log import log
@ -83,7 +83,9 @@ class NoteColours(dbtables.NoteColoursTable):
match = False
for rec in session.scalars(
select(NoteColours)
.filter(NoteColours.enabled.is_(True))
.where(
NoteColours.enabled.is_(True),
)
.order_by(NoteColours.order)
).all():
if rec.is_regex:

View File

@ -45,6 +45,7 @@ from helpers import (
get_embedded_time,
get_relative_date,
ms_to_mmss,
remove_substring_case_insensitive,
set_track_metadata,
)
from log import log
@ -452,7 +453,9 @@ class PlaylistModel(QAbstractTableModel):
if self.is_header_row(row):
with db.Session() as session:
note_foreground = NoteColours.get_colour(session, rat.note, foreground=True)
note_foreground = NoteColours.get_colour(
session, rat.note, foreground=True
)
if note_foreground:
return QBrush(QColor(note_foreground))
@ -738,6 +741,36 @@ class PlaylistModel(QAbstractTableModel):
return None
def load_data(
self, session: db.session, dummy_for_profiling: Optional[int] = None
) -> None:
"""
Same as refresh data, but only used when creating playslit.
Distinguishes profile time between initial load and other
refreshes.
"""
# We used to clear self.playlist_rows each time but that's
# expensive and slow on big playlists
# Note where each playlist_id is
plid_to_row: dict[int, int] = {}
for oldrow in self.playlist_rows:
plrdata = self.playlist_rows[oldrow]
plid_to_row[plrdata.playlistrow_id] = plrdata.row_number
# build a new playlist_rows
new_playlist_rows: dict[int, RowAndTrack] = {}
for p in PlaylistRows.get_playlist_rows(session, self.playlist_id):
if p.id not in plid_to_row:
new_playlist_rows[p.row_number] = RowAndTrack(p)
else:
new_playlist_rows[p.row_number] = self.playlist_rows[plid_to_row[p.id]]
new_playlist_rows[p.row_number].row_number = p.row_number
# Copy to self.playlist_rows
self.playlist_rows = new_playlist_rows
def mark_unplayed(self, row_numbers: list[int]) -> None:
"""
Mark row as unplayed
@ -1007,7 +1040,9 @@ class PlaylistModel(QAbstractTableModel):
self.invalidate_row(track_sequence.previous.row_number)
@line_profiler.profile
def refresh_data(self, session: db.session, dummy_for_profiling=None) -> None:
def refresh_data(
self, session: db.session, dummy_for_profiling: Optional[int] = None
) -> None:
"""
Populate self.playlist_rows with playlist data
@ -1037,66 +1072,6 @@ class PlaylistModel(QAbstractTableModel):
# Copy to self.playlist_rows
self.playlist_rows = new_playlist_rows
# Same as refresh data, but only used when creating playslit.
# Distinguishes profile time between initial load and other
# refreshes.
def load_data(
self, session: db.session, dummy_for_profiling: Optional[int] = None
) -> None:
"""Populate self.playlist_rows with playlist data"""
# Same as refresh data, but only used when creating playslit.
# Distinguishes profile time between initial load and other
# refreshes.
# We used to clear self.playlist_rows each time but that's
# expensive and slow on big playlists
# Note where each playlist_id is
plid_to_row: dict[int, int] = {}
for oldrow in self.playlist_rows:
plrdata = self.playlist_rows[oldrow]
plid_to_row[plrdata.playlistrow_id] = plrdata.row_number
# build a new playlist_rows
new_playlist_rows: dict[int, RowAndTrack] = {}
for p in PlaylistRows.get_playlist_rows(session, self.playlist_id):
if p.id not in plid_to_row:
new_playlist_rows[p.row_number] = RowAndTrack(p)
else:
new_playlist_rows[p.row_number] = self.playlist_rows[plid_to_row[p.id]]
new_playlist_rows[p.row_number].row_number = p.row_number
# Copy to self.playlist_rows
self.playlist_rows = new_playlist_rows
# Same as refresh data, but only used when creating playslit.
# Distinguishes profile time between initial load and other
# refreshes.
def load_data(self, session: db.session, dummy_for_profiling=None) -> None:
"""Populate self.playlist_rows with playlist data"""
# We used to clear self.playlist_rows each time but that's
# expensive and slow on big playlists
# Note where each playlist_id is
plid_to_row: dict[int, int] = {}
for oldrow in self.playlist_rows:
plrdata = self.playlist_rows[oldrow]
plid_to_row[plrdata.playlistrow_id] = plrdata.row_number
# build a new playlist_rows
new_playlist_rows: dict[int, RowAndTrack] = {}
for p in PlaylistRows.get_playlist_rows(session, self.playlist_id):
if p.id not in plid_to_row:
new_playlist_rows[p.row_number] = RowAndTrack(p)
else:
new_playlist_rows[p.row_number] = self.playlist_rows[plid_to_row[p.id]]
new_playlist_rows[p.row_number].row_number = p.row_number
# Copy to self.playlist_rows
self.playlist_rows = new_playlist_rows
def refresh_row(self, session, row_number):
"""Populate dict for one row from database"""
@ -1172,7 +1147,7 @@ class PlaylistModel(QAbstractTableModel):
# Safety check
if not ask_yes_no(
title="Remove comments",
question=f"Remove comments from {len(row_numbers)} rows?"
question=f"Remove comments from {len(row_numbers)} rows?",
):
return
@ -1227,7 +1202,12 @@ class PlaylistModel(QAbstractTableModel):
def remove_section_timer_markers(self, header_text: str) -> str:
"""
Remove characters used to mark section timeings from
passed header text. Return header text witout markers
passed header text.
Remove text using to signal header colours if colour entry
is so marked.
Return header text witout markers
"""
while header_text.endswith(Config.SECTION_STARTS):
@ -1235,6 +1215,31 @@ class PlaylistModel(QAbstractTableModel):
while header_text.endswith(Config.SECTION_ENDINGS):
header_text = header_text[0:-1]
# Parse passed header text and remove the first colour match string
with db.Session() as session:
for rec in NoteColours.get_all(session):
if not rec.strip_substring:
continue
if rec.is_regex:
flags = re.UNICODE
if not rec.is_casesensitive:
flags |= re.IGNORECASE
p = re.compile(rec.substring, flags)
if p.match(header_text):
header_text = re.sub(p, "", header_text)
break
else:
if rec.is_casesensitive:
if rec.substring.lower() in header_text.lower():
header_text = remove_substring_case_insensitive(
header_text, rec.substring
)
break
else:
if rec.substring in header_text:
header_text = header_text.replace(rec.substring, "")
break
return header_text
def rowCount(self, index: QModelIndex = QModelIndex()) -> int:

View File

@ -0,0 +1,44 @@
"""Add strip_substring to NoteColoursTable
Revision ID: a524796269fa
Revises: 708a21f5c271
Create Date: 2024-12-14 12:42:45.214707
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'a524796269fa'
down_revision = '708a21f5c271'
branch_labels = None
depends_on = None
def upgrade(engine_name: str) -> None:
globals()["upgrade_%s" % engine_name]()
def downgrade(engine_name: str) -> None:
globals()["downgrade_%s" % engine_name]()
def upgrade_() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('notecolours', schema=None) as batch_op:
batch_op.add_column(sa.Column('strip_substring', sa.Boolean(), nullable=False))
# ### end Alembic commands ###
def downgrade_() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('notecolours', schema=None) as batch_op:
batch_op.drop_column('strip_substring')
# ### end Alembic commands ###