From 0391eed88eaa7bc920ba96a905d8f8dc7f111091 Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Sat, 14 Dec 2024 14:49:07 +0000 Subject: [PATCH] Optionally remove header colour directives from header --- app/dbtables.py | 1 + app/helpers.py | 26 ++++ app/models.py | 6 +- app/playlistmodel.py | 133 +++++++++--------- ...add_strip_substring_to_notecolourstable.py | 44 ++++++ 5 files changed, 144 insertions(+), 66 deletions(-) create mode 100644 migrations/versions/a524796269fa_add_strip_substring_to_notecolourstable.py diff --git a/app/dbtables.py b/app/dbtables.py index 2e03826..279c198 100644 --- a/app/dbtables.py +++ b/app/dbtables.py @@ -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 ( diff --git a/app/helpers.py b/app/helpers.py index 42acedf..ed22ae2 100644 --- a/app/helpers.py +++ b/app/helpers.py @@ -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 diff --git a/app/models.py b/app/models.py index 000811f..8a0890d 100644 --- a/app/models.py +++ b/app/models.py @@ -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: diff --git a/app/playlistmodel.py b/app/playlistmodel.py index 02dfdc8..d0c01fb 100644 --- a/app/playlistmodel.py +++ b/app/playlistmodel.py @@ -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: diff --git a/migrations/versions/a524796269fa_add_strip_substring_to_notecolourstable.py b/migrations/versions/a524796269fa_add_strip_substring_to_notecolourstable.py new file mode 100644 index 0000000..63e3433 --- /dev/null +++ b/migrations/versions/a524796269fa_add_strip_substring_to_notecolourstable.py @@ -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 ### +