mirror of
https://git.srhtnin.garden/sarah/SaraTag.git
synced 2026-10-15 02:43:17 +02:00
SaraTag, a Flet GUI for tagging files.
This commit is contained in:
commit
845d6c6ee6
27 files changed
+6035
No files matched your search
+153
@@ -0,0 +1,153 @@
|
||||
"""Update: bring Books in line with their Lexicons, for every Record or one.
|
||||
"Preview only" runs the same thing without writing, to see what would change."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import flet as ft
|
||||
|
||||
from Core import ChipButton, DisplayPath, Heading, Note, Palette
|
||||
from History import RecordUpdaterRun, UpdaterState
|
||||
from Library import DataError
|
||||
from Records import ListRecords, Record
|
||||
from Updater import DestructiveModes, Modes, UpdateRecord, UpdateResult
|
||||
from Widgets import App, Panel, Plural, SectionTitle
|
||||
|
||||
AllRecords = "All"
|
||||
|
||||
|
||||
class UpdatePage(ft.Container):
|
||||
def __init__(self, TheApp: App):
|
||||
super().__init__(expand=True, padding=16)
|
||||
self.App = TheApp
|
||||
self.Records: list[Record] = []
|
||||
self.Running = False
|
||||
|
||||
State = UpdaterState()
|
||||
self.ModePicker = ft.RadioGroup(
|
||||
ft.Column([
|
||||
ft.Column([ft.Radio(value=Name, label=Label), ft.Container(Note(Help), padding=ft.Padding.only(left=48))], spacing=0)
|
||||
for Name, (Label, Help) in Modes.items()
|
||||
], spacing=6),
|
||||
value=State.get("LastMode") if State.get("LastMode") in Modes else "Expand",
|
||||
)
|
||||
self.DryRun = ft.Switch(label="Preview only (change nothing, just show what would change)",
|
||||
value=bool(State.get("LastDryRun", False)))
|
||||
self.ScopePicker = ft.Dropdown(label="Which Records", width=360, dense=True)
|
||||
self.LastScope = State.get("LastScope", "")
|
||||
self.RunButton = ChipButton("Run", lambda e: self.Run(), ft.Icons.PLAY_ARROW)
|
||||
self.Progress = ft.Row([ft.ProgressRing(width=16, height=16), Note("")], visible=False)
|
||||
self.Results = ft.Column(spacing=12)
|
||||
Options = ft.Column([
|
||||
Heading("Update Books"),
|
||||
Note("Only directories with a Lexicon are touched. Files never change; only their Books do."),
|
||||
Panel(self.ModePicker),
|
||||
self.DryRun,
|
||||
ft.Row([self.ScopePicker, self.RunButton, self.Progress], spacing=12, vertical_alignment=ft.CrossAxisAlignment.CENTER),
|
||||
], spacing=12, width=560)
|
||||
self.content = ft.Column([Options, ft.Divider(height=1, color=Palette.Surface1), self.Results],
|
||||
spacing=16, scroll=ft.ScrollMode.AUTO, expand=True)
|
||||
|
||||
def Refresh(self, Scope: str = "") -> None:
|
||||
self.page.on_keyboard_event = None
|
||||
try:
|
||||
self.Records = [Item for Item in ListRecords()[0] if Item.Enabled]
|
||||
except DataError as Error:
|
||||
self.App.Notify(str(Error), "error")
|
||||
self.Records = []
|
||||
Options = [ft.DropdownOption(key=AllRecords, text=f"All enabled Records ({len(self.Records)})")]
|
||||
Options += [ft.DropdownOption(key=str(Item.Directory), text=Item.Name) for Item in self.Records]
|
||||
self.ScopePicker.options = Options
|
||||
Wanted = Scope or self.ScopePicker.value or self.LastScope
|
||||
Keys = {Option.key for Option in Options}
|
||||
self.ScopePicker.value = Wanted if Wanted in Keys else AllRecords
|
||||
self.update()
|
||||
|
||||
def Leave(self) -> None:
|
||||
pass
|
||||
|
||||
def _Chosen(self) -> list[Record]:
|
||||
if self.ScopePicker.value == AllRecords:
|
||||
return self.Records
|
||||
return [Item for Item in self.Records if str(Item.Directory) == self.ScopePicker.value]
|
||||
|
||||
def Run(self) -> None:
|
||||
if self.Running:
|
||||
return
|
||||
Mode = self.ModePicker.value or "Expand"
|
||||
Chosen = self._Chosen()
|
||||
if not Chosen:
|
||||
self.App.Notify("There are no enabled Records to update.", "warning")
|
||||
return
|
||||
if Mode in DestructiveModes and not self.DryRun.value:
|
||||
Label = Modes[Mode][0]
|
||||
self.App.Confirm(f"{Label}?", f"{Modes[Mode][1]} This rewrites Books in {Plural(len(Chosen), 'Record')} and "
|
||||
"can't be undone. Tip: run it with Preview only first.", lambda: self._Start(Mode, Chosen),
|
||||
"Run", Accent=Palette.Yellow)
|
||||
return
|
||||
self._Start(Mode, Chosen)
|
||||
|
||||
def _Start(self, Mode: str, Chosen: list[Record]) -> None:
|
||||
self.Running = True
|
||||
self.RunButton.disabled = True
|
||||
self.Progress.visible = True
|
||||
self.Results.controls = []
|
||||
self.update()
|
||||
self.page.run_thread(self._Work, Mode, bool(self.DryRun.value), Chosen)
|
||||
|
||||
def _Work(self, Mode: str, DryRun: bool, Chosen: list[Record]) -> None:
|
||||
Label: ft.Text = self.Progress.controls[1]
|
||||
Results: list[tuple[Record, UpdateResult]] = []
|
||||
|
||||
def Progress(Directory: Path) -> None:
|
||||
Label.value = DisplayPath(str(Directory))
|
||||
Label.update()
|
||||
|
||||
try:
|
||||
for Item in Chosen:
|
||||
Results.append((Item, UpdateRecord(Item.Directory, Item.LexiconPath, Mode, DryRun, Progress)))
|
||||
Changed = [BookPath for _, Result in Results for BookPath in Result.ChangedBooks]
|
||||
RecordUpdaterRun(Mode, DryRun, AllRecords if len(Chosen) > 1 else str(Chosen[0].Directory), Changed)
|
||||
finally:
|
||||
self.Running = False
|
||||
self.RunButton.disabled = False
|
||||
self.Progress.visible = False
|
||||
self._ShowResults(Mode, DryRun, Results)
|
||||
|
||||
def _ShowResults(self, Mode: str, DryRun: bool, Results: list[tuple[Record, UpdateResult]]) -> None:
|
||||
Total = UpdateResult(Path("/"), Mode, DryRun)
|
||||
for _, Result in Results:
|
||||
Total.Merge(Result)
|
||||
Verb = "would change" if DryRun else "changed"
|
||||
Headline = f"{Modes[Mode][0]}{' (preview)' if DryRun else ''}: {Plural(len(Total.ChangedBooks), 'Book')} {Verb}."
|
||||
Rows = [ft.Row([ft.Text(Name, size=13, width=200), ft.Text(str(Count), size=13,
|
||||
color=Palette.Red if Count and Name in ("Errors", "Invalid values") else Palette.Text)])
|
||||
for Name, Count in Total.Summary() if Count or Name in ("Files scanned", "Books created", "Books updated")]
|
||||
Controls: list[ft.Control] = [
|
||||
SectionTitle(Headline),
|
||||
Panel(ft.Column(Rows, spacing=2), width=560),
|
||||
]
|
||||
if len(Results) > 1:
|
||||
PerRecord = [ft.Row([ft.Text(Item.Name, size=13, expand=True),
|
||||
Note(f"{len(Result.ChangedBooks)} {Verb}" + (f", {Plural(len(Result.Errors), 'error')}" if Result.Errors else ""))])
|
||||
for Item, Result in Results]
|
||||
Controls.append(self._Expander("Per Record", PerRecord))
|
||||
for Title, Items, Color in (
|
||||
("Errors", Total.Errors, Palette.Red),
|
||||
("Invalid values", Total.InvalidValues, Palette.Yellow),
|
||||
("Orphaned Books (their file is gone)", [str(Item) for Item in Total.OrphanedBooks], Palette.Peach),
|
||||
(f"Books that {Verb}", [str(Item) for Item in Total.ChangedBooks], Palette.Text),
|
||||
):
|
||||
if Items:
|
||||
Controls.append(self._Expander(f"{Title} ({len(Items)})", [
|
||||
ft.Text(DisplayPath(Text), size=12, color=Color, selectable=True) for Text in Items[:500]
|
||||
] + ([Note(f"…and {len(Items) - 500} more")] if len(Items) > 500 else []), Open=Color == Palette.Red))
|
||||
self.Results.controls = Controls
|
||||
self.update()
|
||||
self.App.Notify(Headline, "error" if Total.Errors else "success")
|
||||
|
||||
@staticmethod
|
||||
def _Expander(Title: str, Controls: list[ft.Control], Open: bool = False) -> ft.Control:
|
||||
return ft.ExpansionTile(
|
||||
title=ft.Text(Title, size=14), expanded=Open, controls=[ft.Container(ft.Column(Controls, spacing=2), padding=12)],
|
||||
collapsed_icon_color=Palette.Mauve, icon_color=Palette.Mauve,
|
||||
)
|
||||
Reference in new issue
Block a user