From f7ed23edf996a35b06bbe906a6bb5d948d92ecae Mon Sep 17 00:00:00 2001 From: SRHTNIN Date: Wed, 7 Oct 2026 22:23:36 +0200 Subject: [PATCH] SaraSort: Flet GUI for configuring SaraSortd Flowchart of inputs/outputs with editable arrows, service page (status, controls, autostart, version, logs), config editor (form + raw), templates, tools (warnings, filename tester, failed files) and settings with SaraFem-compatible export/import. AppImage build and a SaraSuite manifest in Packaging/. Co-Authored-By: Claude Opus 5.5 --- .gitignore | 7 + ConfigPage.py | 438 ++++++++++++ Core.py | 307 ++++++++ FlowPage.py | 560 +++++++++++++++ Main.py | 111 +++ Packaging/BuildAppImage.sh | 38 + Packaging/Launch.sh | 6 + Packaging/SaraSuite/Manifest.toml | 27 + Packaging/SaraSuite/Templates/Launch.sh | 6 + .../SaraSuite/Templates/SaraSort.desktop | 9 + README.md | 64 ++ Service.py | 81 +++ ServicePage.py | 222 ++++++ SettingsPage.py | 196 +++++ Sortd.py | 673 ++++++++++++++++++ Templates.py | 130 ++++ TemplatesPage.py | 168 +++++ ToolsPage.py | 191 +++++ Widgets.py | 162 +++++ assets/Icon.png | Bin 0 -> 1257 bytes assets/fonts/Fixedsys_Excelsior_Regular.ttf | Bin 0 -> 580724 bytes requirements.txt | 3 + 22 files changed, 3399 insertions(+) create mode 100644 .gitignore create mode 100644 ConfigPage.py create mode 100644 Core.py create mode 100644 FlowPage.py create mode 100644 Main.py create mode 100755 Packaging/BuildAppImage.sh create mode 100755 Packaging/Launch.sh create mode 100644 Packaging/SaraSuite/Manifest.toml create mode 100755 Packaging/SaraSuite/Templates/Launch.sh create mode 100644 Packaging/SaraSuite/Templates/SaraSort.desktop create mode 100644 README.md create mode 100644 Service.py create mode 100644 ServicePage.py create mode 100644 SettingsPage.py create mode 100644 Sortd.py create mode 100644 Templates.py create mode 100644 TemplatesPage.py create mode 100644 ToolsPage.py create mode 100644 Widgets.py create mode 100644 assets/Icon.png create mode 100644 assets/fonts/Fixedsys_Excelsior_Regular.ttf create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2b48d2e --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.venv/ +__pycache__/ +*.pyc +build/ +.flet/ +dist/ +*.AppImage diff --git a/ConfigPage.py b/ConfigPage.py new file mode 100644 index 0000000..4c13ed9 --- /dev/null +++ b/ConfigPage.py @@ -0,0 +1,438 @@ +"""Every SaraSortd config in a list on the left (GlobalConf.toml first, then +the two templates, then each directory's own); the chosen one opens on the +right as a form or as raw TOML. + +Saving the form re-reads the file and only writes the values you changed, +since SaraSortd itself keeps updating NextNum, NextChar and LastFile in +output configs while it runs. +""" + +from pathlib import Path +from typing import Any, Callable, Optional + +import flet as ft +import tomlkit +from tomlkit.items import AoT + +from Core import ChipButton, ChipButtonStyle, DisplayPath, Heading, Note, Palette +from Sortd import Setup, SwitchKeys, ReadToml, WriteToml +from Widgets import App, EmptyState, NameField + +ListWidth = 270 + +Help = { + "Title": "Don't change: SaraSortd uses it to tell configs apart.", + "Version": "Config version. The first number has to match SaraSortd's.", + "SafeMode": "Stop SaraSortd when a config has an error.", + "SilentMode": "Don't print what SaraSortd does to the terminal / journal.", + "SortTimer": "Seconds between checks of the input directories.", + "DotFiles": "Also sort files whose names start with \".\".", + "Logging": "Write log files to LogDir.", + "OverwriteDirConf": "Overwrite directory configs with the templates on every start.", + "NextNum": "Variable: the next free number (or, in [[Files]], the next number to use).", + "NextChar": "Variable: the next free letter (or, in [[Files]], the next letter to use).", + "Parent": "Variable: the parent directory's name.", + "OrgFileName": "Variable: the sorted file's original name, without extension.", + "OrgFileType": "Variable: the sorted file's original extension, e.g. .png.", + "Year": "Variable: the current year.", "Month": "Variable: the current month.", "Day": "Variable: the current day.", + "Hour": "Variable: the current hour.", "Minute": "Variable: the current minute.", "Second": "Variable: the current second.", + "VarCall": "The two characters around %Name% lookups.", + "RootDir": "A shorthand other paths can use as %RootDir%. Add more keys like it.", + "InputDir": "Directories whose files get sorted. One per line.", + "OutputDir": "Directories files get sorted into. One per line.", + "LogDir": "Where SaraSortd writes its logs.", + "FailedDir": "Where files go that no output takes.", + "LogFileName": "Name for log files.", + "InDirConfName": "Name of input directories' config files.", + "OutDirConfName": "Name of output directories' config files.", + "FileHistoryName": "Name of the history file used when an input sorts into itself.", + "LastFile": "Updated by SaraSortd: the last file sorted here.", + "ParentDir": "Updated by SaraSortd: this directory's name.", + "ValidInputDirs": "Input directories allowed to sort here; * and ? are wildcards. One per line. " + "In a [[Files]] entry: only for that entry (empty = every allowed input).", + "FileLimit": "Most files this directory may hold (0 = no limit).", + "DeleteOrg": "Move files here (off: copy them).", + "Unzip": "Archive types to unpack here, e.g. zip, tar.gz. One per line.", + "SortDirs": "Also sort directories (they're zipped first).", + "Pattern": "Which files this entry is for; * and ? are wildcards.", + "NewFileName": "The sorted file's new name.", + "CaseSensitive": "Match Pattern case-sensitively.", + "Overwrite": "Let a file with the same name be overwritten.", +} +ListKeys = {"InputDir", "OutputDir", "ValidInputDirs", "Unzip"} + + +class FormBuilder: + """Turns TOML data into controls and remembers how to read each value back.""" + + def __init__(self, TheSetup: Optional[Setup], OutputPath: str, EntryDefaults: dict, OutputStyle: bool): + self.TheSetup = TheSetup + self.OutputPath = OutputPath + self.EntryDefaults = EntryDefaults + self.OutputStyle = OutputStyle + self.Getters: dict[tuple, tuple[Callable[[], Any], Any]] = {} + self.ArrayEditors: dict[tuple, "ArrayEditor"] = {} + + def Field(self, KeyPath: tuple, Key: str, Value: Any, InEntry: bool = False) -> ft.Control: + Tip = Help.get(Key, "") + if Key in SwitchKeys and isinstance(Value, int) and not isinstance(Value, bool) and Value in (0, 1): + Control = ft.Switch(label=Key, value=Value == 1) + self.Getters[KeyPath] = (lambda: 1 if Control.value else 0, Value) + elif isinstance(Value, bool): + Control = ft.Switch(label=Key, value=Value) + self.Getters[KeyPath] = (lambda: bool(Control.value), Value) + elif isinstance(Value, (int, float)): + Control = ft.TextField(label=Key, value=str(Value), dense=True, width=200, + keyboard_type=ft.KeyboardType.NUMBER) + Kind = type(Value) + + def ReadNumber(Control=Control, Kind=Kind, Value=Value): + try: + return Kind(Control.value) + except (TypeError, ValueError): + return Value + + self.Getters[KeyPath] = (ReadNumber, Value) + elif isinstance(Value, list) or Key in ListKeys: + Items = Value if isinstance(Value, list) else [] + Control = ft.TextField(label=Key, value="\n".join(str(Item) for Item in Items), multiline=True, + min_lines=1, max_lines=8, dense=True) + self.Getters[KeyPath] = ( + lambda: [Line.strip() for Line in (Control.value or "").splitlines() if Line.strip()], Value, + ) + elif Key == "NewFileName": + Control = NameField(self.TheSetup, str(Value), self.OutputPath, self.EntryDefaults) + self.Getters[KeyPath] = (lambda: Control.value, Value) + else: + Control = ft.TextField(label=Key, value=str(Value), dense=True) + self.Getters[KeyPath] = (lambda: Control.value or "", Value) + if Tip: + return ft.Column([Control, Note(Tip, 11)], spacing=2) + return Control + + def Table(self, Data: dict, Prefix: tuple = ()) -> list[ft.Control]: + Controls: list[ft.Control] = [] + Sections: list[ft.Control] = [] + for Key, Value in Data.items(): + KeyPath = Prefix + (Key,) + if isinstance(Value, dict): + Sections.append(ft.Text(f"[{'.'.join(KeyPath)}]", color=Palette.Mauve, size=16)) + Sections.extend(self.Table(Value, KeyPath)) + elif isinstance(Value, list) and Value and all(isinstance(Item, dict) for Item in Value) or (Key == "Files" and isinstance(Value, list)): + Editor = ArrayEditor(self, KeyPath, Value) + self.ArrayEditors[KeyPath] = Editor + Sections.append(Editor) + else: + Controls.append(self.Field(KeyPath, Key, Value)) + return Controls + Sections + + def NewEntry(self) -> dict: + if self.OutputStyle: + Entry = {"Pattern": "*"} + Entry.update({Key: Value for Key, Value in self.EntryDefaults.items() if Key != "Pattern"}) + return Entry + return {"Pattern": "*", "CaseSensitive": 0} + + +class ArrayEditor(ft.Column): + """[[Files]]: one card per entry, with add, remove and reorder.""" + + def __init__(self, Builder: FormBuilder, KeyPath: tuple, Entries: list[dict]): + super().__init__(spacing=8) + self.Builder = Builder + self.KeyPath = KeyPath + self.Original = [dict(Entry) for Entry in Entries] + self.Entries = [dict(Entry) for Entry in Entries] + self.Readers: list[Callable[[], dict]] = [] + self._Build() + + def _Build(self) -> None: + self.Readers = [] + Cards = [] + for Index, Entry in enumerate(self.Entries): + Local = FormBuilder(self.Builder.TheSetup, self.Builder.OutputPath, Entry, self.Builder.OutputStyle) + Fields = [] + Keys = list(Entry.keys()) + if self.Builder.OutputStyle and "ValidInputDirs" not in Keys: + Keys.append("ValidInputDirs") # optional per entry (7.1) + for Key in Keys: + Fields.append(Local.Field((Key,), Key, Entry.get(Key, []), InEntry=True)) + + def Reader(Local=Local, Entry=Entry) -> dict: + Result = {} + for (Key,), (Getter, Old) in Local.Getters.items(): + Value = Getter() + if Key == "ValidInputDirs" and Value == [] and "ValidInputDirs" not in Entry: + continue + if Key == "ValidInputDirs" and Value == []: + continue # empty: the entry takes files from every allowed input + Result[Key] = Value + return Result + + self.Readers.append(Reader) + Cards.append(ft.Container( + ft.Column([ + ft.Row([ + ft.Text(f"[[{'.'.join(self.KeyPath)}]] #{Index + 1}", color=Palette.Mauve), + ft.Container(expand=True), + ft.IconButton(ft.Icons.ARROW_UPWARD, tooltip="Move up (earlier entries match first)", + disabled=Index == 0, on_click=lambda e, Index=Index: self._Move(Index, -1)), + ft.IconButton(ft.Icons.ARROW_DOWNWARD, tooltip="Move down", + disabled=Index == len(self.Entries) - 1, on_click=lambda e, Index=Index: self._Move(Index, 1)), + ft.IconButton(ft.Icons.DELETE_OUTLINE, icon_color=Palette.Red, tooltip="Remove entry", + on_click=lambda e, Index=Index: self._Remove(Index)), + ]), + *Fields, + ], spacing=8), + padding=12, border=ft.Border.all(1, Palette.Surface1), border_radius=6, bgcolor=Palette.Mantle, + )) + self.controls = [ + ft.Text(f"[[{'.'.join(self.KeyPath)}]]", color=Palette.Mauve, size=16), + *Cards, + ChipButton("Add entry", lambda e: self._Add(), ft.Icons.ADD), + ] + + def _Snapshot(self) -> None: + self.Entries = [Reader() for Reader in self.Readers] + + def _Add(self) -> None: + self._Snapshot() + self.Entries.append(self.Builder.NewEntry()) + self._Build() + self.update() + + def _Remove(self, Index: int) -> None: + self._Snapshot() + del self.Entries[Index] + self._Build() + self.update() + + def _Move(self, Index: int, Step: int) -> None: + self._Snapshot() + Other = Index + Step + self.Entries[Index], self.Entries[Other] = self.Entries[Other], self.Entries[Index] + self._Build() + self.update() + + def Value(self) -> list[dict]: + return [Reader() for Reader in self.Readers] + + +class ConfigPage(ft.Row): + def __init__(self, TheApp: App): + super().__init__(expand=True, spacing=0, vertical_alignment=ft.CrossAxisAlignment.STRETCH) + self.App = TheApp + self.TheSetup: Optional[Setup] = None + self.Current: Optional[Path] = None + self.Mode = "Form" + self.Builder: Optional[FormBuilder] = None + self.RawField: Optional[ft.TextField] = None + self.List = ft.ListView(spacing=0, padding=6, expand=True) + self.Editor = ft.Container(expand=True) + self.controls = [ + ft.Container( + ft.Column([ + ft.Container(Heading("Configs"), padding=ft.Padding.only(left=10, top=12, bottom=6)), + self.List, + ], spacing=0), + width=ListWidth, bgcolor=Palette.Mantle, + ), + ft.VerticalDivider(width=1, color=Palette.Surface1), + self.Editor, + ] + + def _Entries(self) -> list[tuple[str, list[tuple[str, Path, str]]]]: + """(group, [(label, path, kind)])""" + TheSetup = self.TheSetup + Groups = [("SaraSortd", [("GlobalConf.toml", TheSetup.GlobalPath, "Global")])] + Groups.append(("Templates", [ + ("InputDirConf.toml", TheSetup.TemplatePath("In"), "InTemplate"), + ("OutputDirConf.toml", TheSetup.TemplatePath("Out"), "OutTemplate"), + ])) + Seen: set[Path] = set() + for Title, Kind, Paths in (("Inputs", "In", TheSetup.Inputs), ("Outputs", "Out", TheSetup.Outputs)): + Items = [] + for DirPath in dict.fromkeys(Paths): + ConfPath = TheSetup.ConfFile(DirPath, Kind) + if ConfPath in Seen: + continue + Seen.add(ConfPath) + Items.append((DirPath, ConfPath, Kind)) + Groups.append((Title, Items)) + return Groups + + def Refresh(self, Open: Optional[str] = None) -> None: + TheSetup, Error = self.App.LoadSetup() + self.TheSetup = TheSetup + if TheSetup is None: + self.List.controls = [] + self.Editor.content = EmptyState("Can't read SaraSortd's config.", Error) + self.update() + return + if Open: + self.Current = Path(Open) + elif self.Current is None: + self.Current = TheSetup.GlobalPath + self._BuildList() + self._OpenCurrent() + self.update() + + def _BuildList(self) -> None: + Controls = [] + for Group, Items in self._Entries(): + Controls.append(ft.Container(Note(Group), padding=ft.Padding.only(left=10, top=10, bottom=2))) + if not Items: + Controls.append(ft.Container(Note("none", 11), padding=ft.Padding.only(left=16))) + for Label, ConfPath, Kind in Items: + Missing = not ConfPath.exists() + # Directories: their name, with the path below it. + IsDir = Kind in ("In", "Out") + Controls.append(ft.ListTile( + title=ft.Text(Path(Label).name if IsDir else Label, size=13, no_wrap=True, + overflow=ft.TextOverflow.ELLIPSIS, color=Palette.Overlay0 if Missing else None), + subtitle=ft.Text("not made yet" if Missing else (DisplayPath(Label) if IsDir else ConfPath.name), + size=11, color=Palette.Overlay0, no_wrap=True, overflow=ft.TextOverflow.ELLIPSIS), + tooltip=str(ConfPath), + dense=True, + selected=ConfPath == self.Current, + selected_tile_color=Palette.Surface0, + selected_color=Palette.Mauve, + disabled=Missing, + on_click=lambda e, ConfPath=ConfPath: self._Select(ConfPath), + )) + self.List.controls = Controls + + def _Select(self, ConfPath: Path) -> None: + self.Current = ConfPath + self._BuildList() + self._OpenCurrent() + self.update() + + def _KindOf(self, ConfPath: Path) -> str: + for _, Items in self._Entries(): + for _, ItemPath, Kind in Items: + if ItemPath == ConfPath: + return Kind + return "Global" + + def _OpenCurrent(self) -> None: + ConfPath = self.Current + if ConfPath is None or not ConfPath.exists(): + self.Editor.content = EmptyState("Pick a config on the left.") + return + try: + Text = ConfPath.read_text(encoding="utf-8") + Data = tomlkit.parse(Text).unwrap() + except (OSError, tomlkit.exceptions.TOMLKitError) as Error: + Data = None + Text = ConfPath.read_text(encoding="utf-8", errors="replace") if ConfPath.exists() else "" + self.Mode = "Raw" + self.App.Notify(f"{ConfPath.name} isn't valid TOML ({Error}); showing it raw.", "warning") + + Kind = self._KindOf(ConfPath) + TheSetup = self.TheSetup + OutputStyle = Kind in ("Out", "OutTemplate") or ( + Kind == "In" and TheSetup.SharedConfName() and ConfPath.parent.as_posix() in TheSetup.Outputs + ) + OutputPath = ConfPath.parent.as_posix() if Kind in ("In", "Out") else "/Output" + + FormButton = ChipButton("Form", lambda e: self._SetMode("Form")) + RawButton = ChipButton("Raw", lambda e: self._SetMode("Raw")) + FormButton.style = ChipButtonStyle(Palette.Mauve if self.Mode == "Form" else Palette.Surface1) + RawButton.style = ChipButtonStyle(Palette.Mauve if self.Mode == "Raw" else Palette.Surface1) + FormButton.disabled = Data is None + + if self.Mode == "Form" and Data is not None: + self.Builder = FormBuilder(TheSetup, OutputPath, TheSetup.TemplateFileDefaults(), OutputStyle) + Body = ft.ListView(self.Builder.Table(Data), spacing=12, padding=16, expand=True) + self.RawField = None + else: + self.Builder = None + self.RawField = ft.TextField(value=Text, multiline=True, expand=True, text_size=13, + border_color=Palette.Surface1, min_lines=20) + Body = ft.Container(self.RawField, padding=16, expand=True) + + Notes = { + "Global": "SaraSortd reads this when it starts" + (", so saving restarts it." if self.App.Config.AutoRestart else "; restart it after saving."), + "InTemplate": "Template: copied into every new input directory.", + "OutTemplate": "Template: copied into every new output directory. Its first [[Files]] entry is the default for new arrows.", + "In": "Read by SaraSortd on every check; no restart needed.", + "Out": "Read by SaraSortd on every check; no restart needed. SaraSortd updates NextNum, NextChar and LastFile itself.", + } + self.Editor.content = ft.Column([ + ft.Container(ft.Column([ + ft.Row([ + ft.Text(DisplayPath(str(ConfPath)), color=Palette.Mauve, size=15, selectable=True, expand=True), + FormButton, RawButton, + ChipButton("Revert", lambda e: self._Select(ConfPath), ft.Icons.UNDO), + ChipButton("Save", lambda e: self._Save(), ft.Icons.SAVE_OUTLINED), + ]), + Note(Notes.get(Kind, "")), + ], spacing=4), padding=ft.Padding.only(left=16, right=16, top=12)), + ft.Divider(height=1, color=Palette.Surface1), + Body, + ], spacing=6, expand=True) + + def _SetMode(self, Mode: str) -> None: + self.Mode = Mode + self._OpenCurrent() + self.update() + + def _Save(self) -> None: + ConfPath = self.Current + try: + if self.RawField is not None: + Document = tomlkit.parse(self.RawField.value or "") + WriteToml(ConfPath, Document) + elif self.Builder is not None: + self._SaveForm(ConfPath) + except tomlkit.exceptions.TOMLKitError as Error: + self.App.Notify(f"Not saved, it isn't valid TOML: {Error}", "error") + return + except OSError as Error: + self.App.Notify(f"Couldn't save: {Error}", "error") + return + if self._KindOf(ConfPath) == "Global": + self.App.AfterGlobalChange() + else: + self.App.Notify(f"Saved {ConfPath.name}.", "success") + self.Refresh() + + def _SaveForm(self, ConfPath: Path) -> None: + Document = ReadToml(ConfPath) # fresh: SaraSortd may have changed it meanwhile + for KeyPath, (Getter, Old) in self.Builder.Getters.items(): + New = Getter() + if New == Old: + continue + Container = Document + for Key in KeyPath[:-1]: + Container = Container.setdefault(Key, tomlkit.table()) + Container[KeyPath[-1]] = New + for KeyPath, Editor in self.Builder.ArrayEditors.items(): + New = Editor.Value() + if New == Editor.Original: + continue + Container = Document + for Key in KeyPath[:-1]: + Container = Container.setdefault(Key, tomlkit.table()) + Existing = Container.get(KeyPath[-1]) + if Existing is not None and len(Existing) == len(New) and len(New) == len(Editor.Original): + # Same entries: only touch changed keys, so SaraSortd's + # NextNum/NextChar updates in between aren't lost. + for Index, (Entry, Before) in enumerate(zip(New, Editor.Original)): + Target = Existing[Index] + for Key, Value in Entry.items(): + if Before.get(Key) != Value: + Target[Key] = Value + for Key in set(Before) - set(Entry): + if Key in Target: + del Target[Key] + else: + Array = AoT([]) + for Entry in New: + Table = tomlkit.table() + for Key, Value in Entry.items(): + Table[Key] = Value + Array.append(Table) + Container[KeyPath[-1]] = Array + WriteToml(ConfPath, Document) diff --git a/Core.py b/Core.py new file mode 100644 index 0000000..0c71f2b --- /dev/null +++ b/Core.py @@ -0,0 +1,307 @@ +"""Shared app-wide constants: paths, palette, theme, SaraSort's own config. + +The palette, theming and shared settings mirror SaraSuite's and SaraFem's +Core.py and read the same ~/.config/SaraSuite files, so a theme picked for +one Sara program looks the same in all of them. +""" + +import os +import sys +from dataclasses import asdict, dataclass +from pathlib import Path + +import flet as ft +import tomlkit + +AppName = "SaraSort" +# Bundled in assets/fonts and always registered, so it works as FontFamily +# without being installed. +BundledFontFamily = "Fixedsys Excelsior" +ActiveFontFamily = BundledFontFamily # set by ApplyAppearance, read by BuildTheme + +Home = Path.home() +ConfigHome = Home / ".config" +DataHome = Home / ".local" / "share" + +SharedConfigDir = ConfigHome / "SaraSuite" +ThemesDir = SharedConfigDir / "Themes" +SharedConfigPath = SharedConfigDir / "Config.toml" +ConfigDir = SharedConfigDir / AppName +ConfigPath = ConfigDir / "Config.toml" +LayoutPath = ConfigDir / "Layout.toml" # where flowchart boxes were dragged to +UserTemplatesDir = ConfigDir / "Templates" + +# Inside a PyInstaller build, bundled files are unpacked to sys._MEIPASS. +BundleDir = Path(getattr(sys, "_MEIPASS", Path(__file__).resolve().parent)) +AssetsDir = BundleDir / "assets" + +# Themes SaraFem seeds into ThemesDir. Only other themes travel with an +# export as full palettes (same rule as SaraFem's ExportSettings). +BuiltinThemeNames = {"CatppuccinMocha", "Forest", "Gruvbox"} + + +class Palette: + """Live values for the active theme (see ApplyTheme). Same attribute names + and role mapping as SaraSuite's and SaraFem's Palette.""" + + Mauve = "#cba6f7" + Red = "#f38ba8" + Peach = "#fab387" + Yellow = "#f9e2af" + Green = "#a6e3a1" + Blue = "#89b4fa" + Text = "#cdd6f4" + White = "#cdd6f4" + Overlay0 = "#6c7086" + Surface1 = "#45475a" + Surface0 = "#313244" + Base = "#1e1e2e" + Mantle = "#181825" + Crust = "#11111b" + + +_ThemeRoleMap = { + "Background": "Base", "Surface": "Mantle", "SurfaceAlt": "Surface0", "Border": "Surface1", + "Text": "Text", "TextMuted": "Overlay0", "Black": "Crust", "White": "White", + "Red": "Red", "Orange": "Peach", "Yellow": "Yellow", "Green": "Green", + "Blue": "Blue", "Purple": "Mauve", +} + + +def ApplyTheme(ThemeName: str) -> None: + """Mutate Palette in place from ~/.config/SaraSuite/Themes/.toml. + Keeps the built-in Catppuccin Mocha values if the file is missing. Must + run before any control reads Palette.X.""" + ThemeFile = ThemesDir / f"{ThemeName}.toml" + if not ThemeFile.exists(): + return + try: + RoleValues = tomlkit.parse(ThemeFile.read_text()).unwrap().get("Palette", {}) + except tomlkit.exceptions.TOMLKitError: + return + for RoleName, AttrName in _ThemeRoleMap.items(): + if RoleName in RoleValues: + setattr(Palette, AttrName, RoleValues[RoleName]) + + +def AvailableThemes() -> list[str]: + Names = {ThemePath.stem for ThemePath in ThemesDir.glob("*.toml")} if ThemesDir.is_dir() else set() + Names.add("CatppuccinMocha") + return sorted(Names) + + +@dataclass +class AppConfig: + # True: Theme, FontFamily and HideWindowTitleBar come from the shared + # ~/.config/SaraSuite/Config.toml. False: SaraSort's own values below. + UseSharedSettings: bool = True + Theme: str = "CatppuccinMocha" + FontFamily: str = BundledFontFamily + HideWindowTitleBar: bool = False + # Where SaraSuite installs SaraSortd: GlobalConf.toml and the two + # directory config templates live in SortdConfigDir, SaraSortd.py in + # SortdAppDir, and it runs as a systemd user unit. + SortdConfigDir: str = "~/.config/SaraSuite/SaraSortd" + SortdAppDir: str = "~/.local/share/SaraSuite/Programs/SaraSortd/App" + ServiceName: str = "SaraSortd.service" + # SaraSortd only reads GlobalConf.toml when it starts. + AutoRestart: bool = True + RefreshSeconds: int = 5 # status and log refresh on the Service page + LogLines: int = 500 + LabelLength: int = 22 # arrow labels longer than this are shortened + StartPage: str = "Flow" + + def Save(self) -> None: + ConfigDir.mkdir(parents=True, exist_ok=True) + ConfigPath.write_text(tomlkit.dumps(asdict(self))) + + @property + def GlobalConfPath(self) -> Path: + return Path(self.SortdConfigDir).expanduser() / "GlobalConf.toml" + + @property + def SortdScriptPath(self) -> Path: + return Path(self.SortdAppDir).expanduser() / "SaraSortd.py" + + +def LoadConfig() -> AppConfig: + if not ConfigPath.exists(): + Config = AppConfig() + Config.Save() + return Config + try: + Data = tomlkit.parse(ConfigPath.read_text()).unwrap() + except tomlkit.exceptions.TOMLKitError: + return AppConfig() + KnownFields = set(AppConfig.__dataclass_fields__) + return AppConfig(**{Key: Value for Key, Value in Data.items() if Key in KnownFields}) + + +@dataclass +class SharedSettings: + """~/.config/SaraSuite/Config.toml, shared by every SaraSuite program. + Same schema and fallback rules as SaraSuite's and SaraFem's.""" + + Theme: str = "CatppuccinMocha" + FontFamily: str = BundledFontFamily + HideWindowTitleBar: bool = False + + def Save(self) -> None: + SharedConfigDir.mkdir(parents=True, exist_ok=True) + SharedConfigPath.write_text(tomlkit.dumps(asdict(self))) + + +def _OwnSettings(Config: AppConfig) -> SharedSettings: + return SharedSettings(Config.Theme, Config.FontFamily, Config.HideWindowTitleBar) + + +def LoadSharedSettings(Config: AppConfig) -> SharedSettings: + """The shared file, created from SaraSort's own values if missing.""" + if not SharedConfigPath.exists(): + Shared = _OwnSettings(Config) + Shared.Save() + return Shared + Result = _OwnSettings(Config) # fallback for keys the file doesn't have + try: + Data = tomlkit.parse(SharedConfigPath.read_text()).unwrap() + except tomlkit.exceptions.TOMLKitError: + return Result + for Key in SharedSettings.__dataclass_fields__: + if Key in Data: + setattr(Result, Key, Data[Key]) + return Result + + +def EffectiveSettings(Config: AppConfig) -> SharedSettings: + return LoadSharedSettings(Config) if Config.UseSharedSettings else _OwnSettings(Config) + + +def ExportSettings(Config: AppConfig, DestPath: Path) -> None: + """One portable file in the same format as SaraFem's export: the shared + settings under [SaraSuite] (with a custom theme's palette as ThemeData) + and SaraSort's own under [SaraSort]. Other programs ignore [SaraSort], + and SaraSort ignores their sections.""" + Shared = LoadSharedSettings(Config) + SharedData = asdict(Shared) + if Shared.Theme not in BuiltinThemeNames: + ThemeFile = ThemesDir / f"{Shared.Theme}.toml" + if ThemeFile.exists(): + try: + SharedData["ThemeData"] = tomlkit.parse(ThemeFile.read_text()).unwrap().get("Palette", {}) + except tomlkit.exceptions.TOMLKitError: + pass + DestPath.write_text(tomlkit.dumps({"SaraSuite": SharedData, AppName: asdict(Config)})) + + +def ImportSettings(Config: AppConfig, SourcePath: Path) -> None: + """Applies [SaraSuite] to the shared file and [SaraSort] onto Config, key + by key: keys the file doesn't mention stay as they are, unknown sections + and keys are skipped. Raises TOMLKitError on an unreadable file.""" + Data = tomlkit.parse(SourcePath.read_text()).unwrap() + + SharedData = Data.get("SaraSuite") + if isinstance(SharedData, dict): + ThemeData = SharedData.pop("ThemeData", None) + Merged = asdict(LoadSharedSettings(Config)) + Merged.update({Key: Value for Key, Value in SharedData.items() if Key in SharedSettings.__dataclass_fields__}) + ImportedTheme = Merged.get("Theme") + if isinstance(ThemeData, dict) and ImportedTheme and ImportedTheme not in BuiltinThemeNames: + ThemesDir.mkdir(parents=True, exist_ok=True) + (ThemesDir / f"{ImportedTheme}.toml").write_text(tomlkit.dumps({"Palette": ThemeData})) + SharedSettings(**Merged).Save() + + OwnData = Data.get(AppName) + if isinstance(OwnData, dict): + for Key, Value in OwnData.items(): + if Key in AppConfig.__dataclass_fields__: + setattr(Config, Key, Value) + Config.Save() + + +def ApplyAppearance(Settings: SharedSettings) -> None: + """Theme colours and font; must run before anything reads Palette.X.""" + global ActiveFontFamily + ApplyTheme(Settings.Theme) + ActiveFontFamily = Settings.FontFamily or BundledFontFamily + + +def ChildEnvironment() -> dict[str, str]: + """The environment for child processes (systemctl, journalctl, xdg-open). + A PyInstaller build points LD_LIBRARY_PATH at its own bundled libraries; + system tools need the original back.""" + Environment = dict(os.environ) + if getattr(sys, "frozen", False): + Original = Environment.pop("LD_LIBRARY_PATH_ORIG", None) + if Original is not None: + Environment["LD_LIBRARY_PATH"] = Original + else: + Environment.pop("LD_LIBRARY_PATH", None) + for Key in ("PYTHONHOME", "PYTHONPATH"): + Environment.pop(Key, None) + return Environment + + +def BuildTheme() -> ft.Theme: + ColorScheme = ft.ColorScheme( + primary=Palette.Mauve, + on_primary=Palette.Crust, + primary_container=Palette.Surface0, + on_primary_container=Palette.Mauve, + secondary=Palette.Mauve, + on_secondary=Palette.Crust, + secondary_container=Palette.Surface0, + on_secondary_container=Palette.Mauve, + surface=Palette.Base, + on_surface=Palette.Text, + on_surface_variant=Palette.Overlay0, + surface_container=Palette.Mantle, + surface_container_low=Palette.Mantle, + surface_container_high=Palette.Surface0, + surface_container_highest=Palette.Surface1, + outline=Palette.Surface1, + outline_variant=Palette.Surface1, + error=Palette.Red, + on_error=Palette.Crust, + ) + return ft.Theme( + font_family=ActiveFontFamily, + color_scheme=ColorScheme, + use_material3=True, + scaffold_bgcolor=Palette.Base, + canvas_color=Palette.Base, + dialog_theme=ft.DialogTheme( + bgcolor=Palette.Mantle, + shape=ft.RoundedRectangleBorder(radius=8, side=ft.BorderSide(1, Palette.Mauve)), + title_text_style=ft.TextStyle(color=Palette.Mauve, size=18, font_family=ActiveFontFamily), + ), + ) + + +def ChipButtonStyle(Accent: str = "") -> ft.ButtonStyle: + """SaraView's button look: SurfaceAlt fill, 1px accent border, small radius.""" + return ft.ButtonStyle( + color=Palette.Text, + bgcolor=Palette.Surface0, + side=ft.BorderSide(1, Accent or Palette.Mauve), + shape=ft.RoundedRectangleBorder(radius=4), + ) + + +def ChipButton(Text: str, OnClick, Icon=None, Accent: str = "", Disabled: bool = False) -> ft.OutlinedButton: + return ft.OutlinedButton(content=Text, icon=Icon, style=ChipButtonStyle(Accent), on_click=OnClick, disabled=Disabled) + + +def Note(Text: str, Size: int = 12) -> ft.Text: + return ft.Text(Text, color=Palette.Overlay0, size=Size) + + +def Heading(Text: str) -> ft.Text: + return ft.Text(Text, color=Palette.Mauve, size=20) + + +def DisplayPath(PathText: str) -> str: + """/home/sarah/Sorting -> ~/Sorting, for labels.""" + HomeText = str(Home) + if PathText == HomeText or PathText.startswith(HomeText + "/"): + return "~" + PathText[len(HomeText):] + return PathText diff --git a/FlowPage.py b/FlowPage.py new file mode 100644 index 0000000..416ed17 --- /dev/null +++ b/FlowPage.py @@ -0,0 +1,560 @@ +"""The flowchart: every input (red), output (blue) and both (purple) +directory as a box, with an arrow per input -> output pair labelled with the +file patterns that travel along it. + +Boxes start in columns (inputs left, both middle, outputs right) and can be +dragged; dragged positions are kept in Layout.toml. In edit mode, clicking two +boxes connects them, and clicking an arrow's label edits or removes it. +""" + +import math +import os +from typing import Optional + +import flet as ft +import flet.canvas as cv +import tomlkit + +from Core import ChipButton, ChipButtonStyle, DisplayPath, Heading, LayoutPath, Note, Palette +from Sortd import DirNode, Edge, Setup +from Templates import SaveUserTemplate, SortTemplate +from Widgets import App, DirField, EmptyState, ExpandPath, KindColor, NameField + +NodeWidth = 210 +NodeHeight = 58 +ColumnGap = 330 +RowGap = 96 +Margin = 40 +LabelHeight = 24 +CharWidth = 7.5 # Fixedsys at size 12 + + +def LoadLayout() -> dict[str, tuple[float, float]]: + try: + Data = tomlkit.parse(LayoutPath.read_text()).unwrap() + except (OSError, tomlkit.exceptions.TOMLKitError): + return {} + return {Key: (float(Value[0]), float(Value[1])) for Key, Value in Data.get("Positions", {}).items() + if isinstance(Value, list) and len(Value) == 2} + + +def SaveLayout(Positions: dict[str, tuple[float, float]]) -> None: + LayoutPath.parent.mkdir(parents=True, exist_ok=True) + LayoutPath.write_text(tomlkit.dumps({"Positions": {Key: [round(X), round(Y)] for Key, (X, Y) in Positions.items()}})) + + +def ClipToBox(CenterX: float, CenterY: float, TowardX: float, TowardY: float) -> tuple[float, float]: + """Where the line from a box's centre toward a point leaves the box.""" + DX, DY = TowardX - CenterX, TowardY - CenterY + if DX == 0 and DY == 0: + return CenterX, CenterY + Scale = min( + (NodeWidth / 2) / abs(DX) if DX else math.inf, + (NodeHeight / 2) / abs(DY) if DY else math.inf, + ) + return CenterX + DX * Scale, CenterY + DY * Scale + + +class FlowPage(ft.Column): + def __init__(self, TheApp: App): + super().__init__(expand=True, spacing=0) + self.App = TheApp + self.EditMode = False + self.Selected: Optional[str] = None + self.Positions: dict[str, tuple[float, float]] = {} + self.Nodes: dict[str, DirNode] = {} + self.EdgeList: list[Edge] = [] + self.TheSetup: Optional[Setup] = None + self.NodeControls: dict[str, ft.GestureDetector] = {} + self.DragStart: dict = {} + + self.EditButton = ChipButton("Edit mode", self._ToggleEdit, ft.Icons.EDIT_OUTLINED) + self.AddButton = ChipButton("Add directory", self._AddDirectoryDialog, ft.Icons.CREATE_NEW_FOLDER_OUTLINED) + self.AddButton.visible = False + self.Hint = Note("") + self.Canvas = cv.Canvas(shapes=[]) + self.Board = ft.Stack([], width=800, height=600) + self.Body = ft.Container(expand=True) + + def Legend(Text: str, Color: str) -> ft.Row: + return ft.Row([ + ft.Container(width=14, height=14, bgcolor=ft.Colors.with_opacity(0.25, Color), border=ft.Border.all(2, Color), border_radius=3), + ft.Text(Text, size=12), + ], spacing=6) + + self.controls = [ + ft.Container( + ft.Row([ + Heading("Flow"), + ft.Container(width=12), + Legend("Input", Palette.Red), Legend("Output", Palette.Blue), Legend("Both", Palette.Mauve), + ft.Container(expand=True), + self.AddButton, + ChipButton("Tidy layout", self._ResetLayout, ft.Icons.AUTO_FIX_HIGH_OUTLINED), + ChipButton("Reload", lambda e: self.Refresh(), ft.Icons.REFRESH), + self.EditButton, + ], vertical_alignment=ft.CrossAxisAlignment.CENTER), + padding=ft.Padding.only(left=16, right=16, top=12, bottom=4), + ), + ft.Container(self.Hint, padding=ft.Padding.only(left=16, bottom=8)), + ft.Divider(height=1, color=Palette.Surface1), + self.Body, + ] + + # Loading and layout + + def Refresh(self) -> None: + TheSetup, Error = self.App.LoadSetup() + self.TheSetup = TheSetup + if TheSetup is None: + self.Body.content = EmptyState("Can't read SaraSortd's config.", f"{Error}\nCheck the paths in Settings.") + self.update() + return + self.Nodes = TheSetup.Nodes() + self.EdgeList = TheSetup.Edges() + self._Layout() + self._BuildBoard() + self.Body.content = ft.Column( + [ft.Row([self.Board], scroll=ft.ScrollMode.AUTO)], + scroll=ft.ScrollMode.AUTO, expand=True, + ) if self.Nodes else EmptyState("No directories yet.", "Turn on Edit mode and add one, or install a template.") + self._UpdateHint() + self.update() + + def _AutoPositions(self) -> dict[str, tuple[float, float]]: + Columns: dict[str, list[str]] = {"Input": [], "Both": [], "Output": []} + for PathText, Node in self.Nodes.items(): + Columns[Node.Kind].append(PathText) + Used = [Kind for Kind in ("Input", "Both", "Output") if Columns[Kind]] + Result = {} + for ColumnIndex, Kind in enumerate(Used): + for RowIndex, PathText in enumerate(Columns[Kind]): + Result[PathText] = (Margin + ColumnIndex * ColumnGap, Margin + 30 + RowIndex * RowGap) + return Result + + def _Layout(self) -> None: + Auto = self._AutoPositions() + Saved = LoadLayout() + self.Positions = {PathText: Saved.get(PathText, Auto[PathText]) for PathText in self.Nodes} + + def _ResetLayout(self, e) -> None: + self.Positions = self._AutoPositions() + SaveLayout({}) + self._BuildBoard() + self.Board.update() + + def _BoardSize(self) -> tuple[float, float]: + if not self.Positions: + return 800, 600 + Width = max(X for X, Y in self.Positions.values()) + NodeWidth + Margin + 80 + Height = max(Y for X, Y in self.Positions.values()) + NodeHeight + Margin + 40 + return max(Width, 800), max(Height, 500) + + # Drawing + + def _BuildBoard(self) -> None: + self.NodeControls = {PathText: self._NodeControl(Node) for PathText, Node in self.Nodes.items()} + self._Redraw(Update=False) + + def _Redraw(self, Update: bool = True) -> None: + Shapes, Labels = self._EdgeDrawing() + self.Canvas.shapes = Shapes + Width, Height = self._BoardSize() + self.Canvas.width, self.Canvas.height = Width, Height + self.Board.width, self.Board.height = Width, Height + for PathText, Control in self.NodeControls.items(): + Control.left, Control.top = self.Positions[PathText] + self.Board.controls = [self.Canvas] + Labels + list(self.NodeControls.values()) + if Update: + self.Board.update() + + def _NodeControl(self, Node: DirNode) -> ft.GestureDetector: + Color = KindColor(Node.Kind) + Selected = Node.Path == self.Selected + Name = os.path.basename(Node.Path) or Node.Path + Exists = os.path.isdir(Node.Path) + Box = ft.Container( + ft.Column([ + ft.Row([ + ft.Icon(ft.Icons.FOLDER_OUTLINED if Exists else ft.Icons.FOLDER_OFF_OUTLINED, color=Color, size=16), + ft.Text(Name, size=15, color=Palette.Text, no_wrap=True, overflow=ft.TextOverflow.ELLIPSIS, expand=True), + ], spacing=6), + ft.Text(DisplayPath(os.path.dirname(Node.Path)), size=11, color=Palette.Overlay0, no_wrap=True, + overflow=ft.TextOverflow.ELLIPSIS), + ], spacing=2, alignment=ft.MainAxisAlignment.CENTER), + width=NodeWidth, height=NodeHeight, + padding=ft.Padding.symmetric(horizontal=10, vertical=6), + bgcolor=ft.Colors.with_opacity(0.18, Color) if not Selected else ft.Colors.with_opacity(0.4, Color), + border=ft.Border.all(3 if Selected else 2, Palette.Yellow if Selected else Color), + border_radius=6, + tooltip=f"{Node.Path}\n{Node.Kind}" + ("" if Exists else "\n(doesn't exist yet)"), + ) + return ft.GestureDetector( + content=Box, + mouse_cursor=ft.MouseCursor.CLICK, + drag_interval=16, + on_tap=lambda e, PathText=Node.Path: self._NodeClicked(PathText), + on_pan_start=lambda e, PathText=Node.Path: self._DragStart(PathText, e), + on_pan_update=lambda e, PathText=Node.Path: self._DragUpdate(PathText, e), + on_pan_end=lambda e: SaveLayout(self.Positions), + ) + + def _DragStart(self, PathText: str, e) -> None: + self.DragStart = {"Pointer": (e.global_position.x, e.global_position.y), "Box": self.Positions[PathText]} + + def _DragUpdate(self, PathText: str, e) -> None: + if not self.DragStart: + return + StartX, StartY = self.DragStart["Pointer"] + BoxX, BoxY = self.DragStart["Box"] + self.Positions[PathText] = ( + max(0, BoxX + e.global_position.x - StartX), + max(0, BoxY + e.global_position.y - StartY), + ) + self._Redraw() + + def _EdgeDrawing(self) -> tuple[list[cv.Shape], list[ft.Control]]: + Shapes: list[cv.Shape] = [] + Labels: list[ft.Control] = [] + Segments: list = [] + Pairs = {(FoundEdge.Input, FoundEdge.Output) for FoundEdge in self.EdgeList} + for FoundEdge in self.EdgeList: + if FoundEdge.Input not in self.Positions or FoundEdge.Output not in self.Positions: + continue + Color = Palette.Overlay0 + Paint = ft.Paint(color=Color, stroke_width=2, style=ft.PaintingStyle.STROKE) + AX, AY = self.Positions[FoundEdge.Input] + BX, BY = self.Positions[FoundEdge.Output] + if FoundEdge.Input == FoundEdge.Output: + # Sorting into itself: a loop over the top of the box. + Left, Top = AX + NodeWidth * 0.3, AY + Right = AX + NodeWidth * 0.7 + Shapes.append(cv.Path([ + cv.Path.MoveTo(Left, Top), + cv.Path.CubicTo(Left - 10, Top - 50, Right + 10, Top - 50, Right, Top), + ], paint=Paint)) + Shapes.extend(self._ArrowHead(Right + 6, Top - 14, Right, Top, Color)) + Labels.append(self._Label(FoundEdge, AX + NodeWidth / 2, Top - 46)) + continue + ACX, ACY = AX + NodeWidth / 2, AY + NodeHeight / 2 + BCX, BCY = BX + NodeWidth / 2, BY + NodeHeight / 2 + # Two arrows between the same boxes (both ways) are pushed apart. + OffsetX = OffsetY = 0.0 + if (FoundEdge.Output, FoundEdge.Input) in Pairs: + Length = math.hypot(BCX - ACX, BCY - ACY) or 1 + OffsetX, OffsetY = -(BCY - ACY) / Length * 12, (BCX - ACX) / Length * 12 + StartX, StartY = ClipToBox(ACX, ACY, BCX, BCY) + EndX, EndY = ClipToBox(BCX, BCY, ACX, ACY) + StartX, StartY, EndX, EndY = StartX + OffsetX, StartY + OffsetY, EndX + OffsetX, EndY + OffsetY + Shapes.append(cv.Line(StartX, StartY, EndX, EndY, paint=Paint)) + Shapes.extend(self._ArrowHead(StartX, StartY, EndX, EndY, Color)) + Segments.append((FoundEdge, StartX, StartY, EndX, EndY)) + Labels.extend(self._PlaceLabels(Segments)) + return Shapes, Labels + + def _LabelText(self, FoundEdge: Edge) -> tuple[str, str, float]: + Full = ", ".join(FoundEdge.Patterns) + Limit = max(6, self.App.Config.LabelLength) + Short = Full if len(Full) <= Limit else Full[: Limit - 1] + "…" + return Short, Full, len(Short) * CharWidth + 18 + + def _PlaceLabels(self, Segments: list) -> list[ft.Control]: + """Each label goes on its own arrow, slid along it until it's clear of + boxes, other labels and other arrows (or as clear as it gets).""" + Boxes = [(X, Y, X + NodeWidth, Y + NodeHeight) for X, Y in self.Positions.values()] + Placed: list[tuple[float, float, float, float]] = [] + Labels = [] + + def Overlaps(A, B) -> bool: + return A[0] < B[2] and B[0] < A[2] and A[1] < B[3] and B[1] < A[3] + + def Crossings(Rect, Own) -> int: + Count = 0 + for Other in Segments: + if Other is Own: + continue + _, SX, SY, EX, EY = Other + if any(Rect[0] <= SX + (EX - SX) * Step / 24 <= Rect[2] and Rect[1] <= SY + (EY - SY) * Step / 24 <= Rect[3] + for Step in range(25)): + Count += 1 + return Count + + for Segment in Segments: + FoundEdge, SX, SY, EX, EY = Segment + _, _, Width = self._LabelText(FoundEdge) + Best = None + for Fraction in (0.5, 0.4, 0.6, 0.3, 0.7, 0.25, 0.75): + CX, CY = SX + (EX - SX) * Fraction, SY + (EY - SY) * Fraction + Rect = (CX - Width / 2 - 4, CY - LabelHeight / 2 - 2, CX + Width / 2 + 4, CY + LabelHeight / 2 + 2) + Score = 10 * sum(Overlaps(Rect, Other) for Other in Placed + Boxes) + Crossings(Rect, Segment) + if Best is None or Score < Best[0]: + Best = (Score, CX, CY, Rect) + if Score == 0: + break + Placed.append(Best[3]) + Labels.append(self._Label(FoundEdge, Best[1], Best[2])) + return Labels + + def _ArrowHead(self, FromX: float, FromY: float, ToX: float, ToY: float, Color: str) -> list[cv.Shape]: + Angle = math.atan2(ToY - FromY, ToX - FromX) + Size = 11 + Points = [ + (ToX - Size * math.cos(Angle - 0.45), ToY - Size * math.sin(Angle - 0.45)), + (ToX - Size * math.cos(Angle + 0.45), ToY - Size * math.sin(Angle + 0.45)), + ] + return [cv.Path([ + cv.Path.MoveTo(ToX, ToY), cv.Path.LineTo(*Points[0]), cv.Path.LineTo(*Points[1]), cv.Path.Close(), + ], paint=ft.Paint(color=Color, style=ft.PaintingStyle.FILL))] + + def _Label(self, FoundEdge: Edge, CenterX: float, CenterY: float) -> ft.Container: + Short, Full, Width = self._LabelText(FoundEdge) + return ft.Container( + ft.Text(Short, size=12, color=Palette.Text, no_wrap=True), + left=CenterX - Width / 2, top=CenterY - LabelHeight / 2, width=Width, height=LabelHeight, + alignment=ft.Alignment.CENTER, + bgcolor=Palette.Mantle, border=ft.Border.all(1, Palette.Mauve if self.EditMode else Palette.Surface1), + border_radius=4, tooltip=Full if Short != Full else None, + on_click=lambda e: self._EdgeDialog(FoundEdge), + ) + + # Interaction + + def _UpdateHint(self) -> None: + if not self.EditMode: + self.Hint.value = "Click a box for details, an arrow's label for all of its patterns. Drag boxes to move them." + elif self.Selected: + self.Hint.value = f"Now click the directory {os.path.basename(self.Selected)} should sort into (or it again to cancel)." + else: + self.Hint.value = "Edit mode: click a directory, then the one it should sort into. Click a label to change or remove an arrow." + self.Hint.color = Palette.Yellow if self.EditMode else Palette.Overlay0 + + def _ToggleEdit(self, e) -> None: + self.EditMode = not self.EditMode + self.Selected = None + self.EditButton.content = "Done" if self.EditMode else "Edit mode" + self.EditButton.style = ChipButtonStyle(Palette.Yellow if self.EditMode else "") + self.AddButton.visible = self.EditMode + self._UpdateHint() + if self.TheSetup: + self._BuildBoard() + self.update() + + def _NodeClicked(self, PathText: str) -> None: + if not self.EditMode: + self._NodeDialog(PathText) + return + if self.Selected is None: + self.Selected = PathText + elif self.Selected == PathText: + self.Selected = None + else: + Source, self.Selected = self.Selected, None + self._ConnectDialog(Source, PathText) + self._UpdateHint() + self._BuildBoard() + self.update() + + def _AfterChange(self, GlobalChanged: bool) -> None: + if GlobalChanged: + self.App.AfterGlobalChange() + self.Refresh() + + def _ConnectDialog(self, InputPath: str, OutputPath: str, Preset: Optional[list[str]] = None) -> None: + TheSetup = self.TheSetup + Defaults = TheSetup.TemplateFileDefaults() + PatternField = ft.TextField( + label="File types (patterns), separated by commas", value=", ".join(Preset or []), + hint_text="*.png, *.jp*g", autofocus=True, dense=True, + ) + Name = NameField(TheSetup, str(Defaults.get("NewFileName", "~^")), OutputPath, Defaults) + Move = ft.Switch(label="Move files (off: copy them)", value=True) + OutConf = TheSetup.LoadDirConf(OutputPath, "Out") or {} + Move.value = OutConf.get("DeleteOrg", 1) == 1 + + def Save(e) -> None: + Patterns = [Part.strip() for Part in (PatternField.value or "").split(",") if Part.strip()] + if not Patterns: + PatternField.error = "Add at least one pattern, e.g. *.png" + PatternField.update() + return + self.App.Close() + Overrides = {} + if Name.value and Name.value != Defaults.get("NewFileName"): + Overrides["NewFileName"] = Name.value + try: + Changed = TheSetup.Connect(InputPath, OutputPath, Patterns, Overrides, {"DeleteOrg": 1 if Move.value else 0}) + except Exception as Error: + self.App.Notify(f"Couldn't connect: {Error}", "error") + return + self._AfterChange(Changed) + + self.App.ShowDialog( + "Connect", + ft.Column([ + ft.Text(f"{DisplayPath(InputPath)} -> {DisplayPath(OutputPath)}", color=Palette.Mauve, selectable=True), + PatternField, + Note("* is any text, ? one character. Each pattern becomes its own [[Files]] entry; the other " + "settings (NextNum, Overwrite...) come from OutputDirConf.toml."), + Name, + Move, + ], tight=True, spacing=10), + [ChipButton("Cancel", lambda e: self.App.Close()), ChipButton("Connect", Save)], + Width=620, + ) + + def _EdgeDialog(self, FoundEdge: Edge) -> None: + TheSetup = self.TheSetup + Rows = [] + for Rule in FoundEdge.Rules: + Scope = "only from this input" if Rule.OwnInputs else "from every input this output accepts" + Row = [ + ft.Text(Rule.Pattern, color=Palette.Mauve, width=150, selectable=True), + ft.Column([ + ft.Text(f"named {Rule.NewFileName or '~^'}", size=12), + Note(Scope, 11), + ], spacing=0, expand=True), + ] + if self.EditMode: + Row.append(ft.IconButton( + ft.Icons.DELETE_OUTLINE, icon_color=Palette.Red, tooltip="Stop sending this pattern here", + on_click=lambda e, Index=Rule.Index: RemoveOne(Index), + )) + Rows.append(ft.Row(Row)) + + def RemoveOne(Index: int) -> None: + self.App.Close() + Message = TheSetup.RemoveRule(FoundEdge.Input, FoundEdge.Output, Index) + if Message: + self.App.Notify(Message, "warning") + self.Refresh() + + def RemoveAll() -> None: + Messages = TheSetup.Disconnect(FoundEdge.Input, FoundEdge.Output) + if Messages: + self.App.Notify(" ".join(Messages), "warning") + self.Refresh() + + def AddMore(e) -> None: + self.App.Close() + self._ConnectDialog(FoundEdge.Input, FoundEdge.Output) + + def SaveTemplate(e) -> None: + self.App.Close() + self._SaveTemplateDialog(FoundEdge) + + Actions = [ChipButton("Save as template", SaveTemplate, ft.Icons.BOOKMARK_ADD_OUTLINED)] + if self.EditMode: + Actions += [ + ChipButton("Add patterns", AddMore, ft.Icons.ADD), + ChipButton("Remove arrow", lambda e: (self.App.Close(), self.App.Confirm( + "Remove arrow", f"Stop sorting from {DisplayPath(FoundEdge.Input)} into {DisplayPath(FoundEdge.Output)}?", RemoveAll, + )), ft.Icons.LINK_OFF, Accent=Palette.Red), + ] + Actions.append(ChipButton("Close", lambda e: self.App.Close())) + self.App.ShowDialog( + "Arrow", + ft.Column([ + ft.Text(f"{DisplayPath(FoundEdge.Input)} -> {DisplayPath(FoundEdge.Output)}", color=Palette.Mauve, selectable=True), + ft.Divider(height=1, color=Palette.Surface1), + ft.Column(Rows, scroll=ft.ScrollMode.AUTO, height=min(360, 48 * len(Rows) + 8), spacing=4), + Note("SaraSortd tries outputs in OutputDir order and patterns top to bottom; the first match wins."), + ], tight=True, spacing=8), + Actions, Width=620, + ) + + def _SaveTemplateDialog(self, FoundEdge: Edge) -> None: + NameInput = ft.TextField(label="Template name", value=os.path.basename(FoundEdge.Output), autofocus=True, dense=True) + Description = ft.TextField(label="Description", dense=True) + NewNames = {Rule.NewFileName for Rule in FoundEdge.Rules} + + def Save(e) -> None: + if not (NameInput.value or "").strip(): + return + self.App.Close() + OutConf = self.TheSetup.LoadDirConf(FoundEdge.Output, "Out") or {} + Settings = {Key: OutConf[Key] for Key in ("DeleteOrg", "Unzip") if Key in OutConf} + Saved = SaveUserTemplate(SortTemplate( + NameInput.value.strip(), Description.value or "", Patterns=FoundEdge.Patterns, + NewFileName=NewNames.pop() if len(NewNames) == 1 else "", + SuggestedOutput=DisplayPath(FoundEdge.Output), OutputSettings=Settings, + )) + self.App.Notify(f"Saved {Saved.name} in Templates.", "success") + + self.App.ShowDialog("Save as template", ft.Column([NameInput, Description], tight=True), + [ChipButton("Cancel", lambda e: self.App.Close()), ChipButton("Save", Save)], Width=440) + + def _NodeDialog(self, PathText: str) -> None: + Node = self.Nodes[PathText] + TheSetup = self.TheSetup + Incoming = [FoundEdge for FoundEdge in self.EdgeList if FoundEdge.Output == PathText] + Outgoing = [FoundEdge for FoundEdge in self.EdgeList if FoundEdge.Input == PathText] + + def Lines(Edges: list[Edge], Other: str) -> list[ft.Control]: + return [ft.Text(f"{os.path.basename(getattr(FoundEdge, Other))}: {', '.join(FoundEdge.Patterns)}", size=12, selectable=True) + for FoundEdge in Edges] or [Note("none")] + + Kinds = (["In"] if Node.IsInput else []) + (["Out"] if Node.IsOutput else []) + ConfButtons = [] + for Kind in dict.fromkeys(Kinds): + ConfPath = TheSetup.ConfFile(PathText, Kind) + if ConfPath.exists(): + ConfButtons.append(ChipButton( + f"Edit {'input' if Kind == 'In' else 'output'} config" if len(set(TheSetup.ConfFile(PathText, K) for K in Kinds)) > 1 else "Edit config", + lambda e, ConfPath=ConfPath: (self.App.Close(), self.App.Navigate("Configs", Open=str(ConfPath))), + ft.Icons.TUNE, + )) + if len(set(TheSetup.ConfFile(PathText, K) for K in Kinds)) == 1: + break + + def Remove() -> None: + if TheSetup.RemoveDir(PathText): + Positions = LoadLayout() + Positions.pop(PathText, None) + SaveLayout(Positions) + self._AfterChange(True) + + Actions = ConfButtons + [ChipButton("Open folder", lambda e: self.App.OpenFolder(PathText), ft.Icons.FOLDER_OPEN_OUTLINED)] + Actions.append(ChipButton("Remove", lambda e: (self.App.Close(), self.App.Confirm( + "Remove directory", + f"Take {DisplayPath(PathText)} out of SaraSortd? The folder, its files and its config stay on disk.", Remove, + )), ft.Icons.DELETE_OUTLINE, Accent=Palette.Red)) + Actions.append(ChipButton("Close", lambda e: self.App.Close())) + self.App.ShowDialog( + os.path.basename(PathText) or PathText, + ft.Column([ + ft.Text(PathText, selectable=True, color=KindColor(Node.Kind)), + Note(f"{Node.Kind}" + ("" if os.path.isdir(PathText) else " - doesn't exist yet")), + ft.Divider(height=1, color=Palette.Surface1), + ft.Text("Receives", color=Palette.Mauve), *Lines(Incoming, "Input"), + ft.Text("Sends", color=Palette.Mauve), *Lines(Outgoing, "Output"), + ], tight=True, spacing=6, scroll=ft.ScrollMode.AUTO), + Actions, Width=620, + ) + + def _AddDirectoryDialog(self, e) -> None: + if not self.TheSetup: + return + Row, Field = DirField(self.App, "Directory") + Kind = ft.RadioGroup(ft.Row([ + ft.Radio(value="InputDir", label="Input"), ft.Radio(value="OutputDir", label="Output"), + ft.Radio(value="Both", label="Both"), + ]), value="InputDir") + + def Add(e) -> None: + PathText = ExpandPath(Field.value or "") + if not PathText: + return + self.App.Close() + Keys = ["InputDir", "OutputDir"] if Kind.value == "Both" else [Kind.value] + try: + Changed = self.TheSetup.AddDirectory(PathText, Keys) + except OSError as Error: + self.App.Notify(f"Couldn't add it: {Error}", "error") + return + self._AfterChange(Changed) + + self.App.ShowDialog("Add directory", ft.Column([ + Row, Kind, + Note("Its config is made from InputDirConf.toml / OutputDirConf.toml, like SaraSortd does. " + "A new output takes every file (the template's \"*\") until you change it."), + ], tight=True, spacing=10), [ChipButton("Cancel", lambda e: self.App.Close()), ChipButton("Add", Add)], Width=560) diff --git a/Main.py b/Main.py new file mode 100644 index 0000000..75aa261 --- /dev/null +++ b/Main.py @@ -0,0 +1,111 @@ +"""SaraSort: a GUI for configuring SaraSortd. It doesn't sort anything itself; +it reads and edits SaraSortd's configs and controls its systemd service.""" + +import flet as ft + +from ConfigPage import ConfigPage +from Core import ( + AppName, ApplyAppearance, AssetsDir, BuildTheme, BundledFontFamily, EffectiveSettings, LoadConfig, Palette, +) +from FlowPage import FlowPage +from ServicePage import ServicePage +from SettingsPage import SettingsPage +from TemplatesPage import TemplatesPage +from ToolsPage import ToolsPage +from Widgets import App + +Destinations = [ + ("Flow", ft.Icons.SCHEMA_OUTLINED, ft.Icons.SCHEMA), + ("Service", ft.Icons.MONITOR_HEART_OUTLINED, ft.Icons.MONITOR_HEART), + ("Configs", ft.Icons.TUNE_OUTLINED, ft.Icons.TUNE), + ("Templates", ft.Icons.DASHBOARD_CUSTOMIZE_OUTLINED, ft.Icons.DASHBOARD_CUSTOMIZE), + ("Tools", ft.Icons.BUILD_OUTLINED, ft.Icons.BUILD), + ("Settings", ft.Icons.SETTINGS_OUTLINED, ft.Icons.SETTINGS), +] +Names = [Name for Name, _, _ in Destinations] + + +def Main(Page: ft.Page) -> None: + Config = LoadConfig() + Appearance = EffectiveSettings(Config) + ApplyAppearance(Appearance) # before anything reads Palette.X + + Page.title = AppName + Page.theme_mode = ft.ThemeMode.DARK + Page.theme = BuildTheme() + Page.dark_theme = BuildTheme() + Page.fonts = {BundledFontFamily: "fonts/Fixedsys_Excelsior_Regular.ttf"} + Page.window.title_bar_hidden = Appearance.HideWindowTitleBar + Page.padding = 0 + Page.bgcolor = Palette.Base + Page.window.width = 1180 + Page.window.height = 780 + Page.window.min_width = 760 + Page.window.min_height = 520 + + TheApp = App(Page, Config) + Body = ft.Container(expand=True) + Pages: dict[str, ft.Control] = {} + State = {"Current": ""} + + def Navigate(Name: str, **Arguments) -> None: + if Name not in Pages: + return + Previous = Pages.get(State["Current"]) + if isinstance(Previous, ServicePage): + Previous.StopPolling() + State["Current"] = Name + Rail.selected_index = Names.index(Name) + Body.content = Pages[Name] + Page.update() + Shown = Pages[Name] + Shown.Refresh(**Arguments) + if isinstance(Shown, ServicePage): + Shown.StartPolling() + + TheApp.Navigate = Navigate + Pages.update({ + "Flow": FlowPage(TheApp), + "Service": ServicePage(TheApp), + "Configs": ConfigPage(TheApp), + "Templates": TemplatesPage(TheApp), + "Tools": ToolsPage(TheApp), + "Settings": SettingsPage(TheApp, OnSaved=lambda: None), + }) + + Rail = ft.NavigationRail( + selected_index=0, + label_type=ft.NavigationRailLabelType.ALL, + bgcolor=Palette.Mantle, + indicator_color=Palette.Surface0, + min_width=88, + destinations=[ft.NavigationRailDestination(icon=Icon, selected_icon=Selected, label=Name) + for Name, Icon, Selected in Destinations], + on_change=lambda e: Navigate(Names[e.control.selected_index]), + ) + + async def CloseWindow(e) -> None: + await Page.window.close() + + TitleRow = [ft.WindowDragArea( + ft.Container(ft.Text(AppName, size=18, color=Palette.Mauve), padding=ft.Padding.only(left=16, top=6, bottom=6)), + expand=True, + )] + if Appearance.HideWindowTitleBar: + TitleRow += [ + ft.IconButton(ft.Icons.MINIMIZE, icon_size=16, tooltip="Minimize", + on_click=lambda e: (setattr(Page.window, "minimized", True), Page.update())), + ft.IconButton(ft.Icons.CLOSE, icon_size=16, tooltip="Close", on_click=CloseWindow), + ] + + Page.add(ft.Column([ + ft.Container(ft.Row(TitleRow, spacing=0), bgcolor=Palette.Mantle, + border=ft.Border.only(bottom=ft.BorderSide(1, Palette.Surface1))), + ft.Row([Rail, ft.VerticalDivider(width=1, color=Palette.Surface1), Body], expand=True, spacing=0, + vertical_alignment=ft.CrossAxisAlignment.STRETCH), + ], expand=True, spacing=0)) + Navigate(Config.StartPage if Config.StartPage in Pages else "Flow") + + +if __name__ == "__main__": + ft.run(Main, assets_dir=str(AssetsDir)) diff --git a/Packaging/BuildAppImage.sh b/Packaging/BuildAppImage.sh new file mode 100755 index 0000000..09ada55 --- /dev/null +++ b/Packaging/BuildAppImage.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# Builds dist/SaraSort-x86_64.AppImage: a PyInstaller one-file build (with +# the font and icon bundled) wrapped by appimagetool, the same way SaraSuite's +# and SaraFem's AppImages are made. Needs appimagetool on PATH. +set -euo pipefail +cd "$(dirname "$(readlink -f "$0")")/.." + +[ -d .venv ] || python3 -m venv .venv +.venv/bin/pip install --quiet --disable-pip-version-check -r requirements.txt pyinstaller + +# --collect-data flet: without it ft.Icons crashes when frozen. +.venv/bin/pyinstaller --noconfirm --clean --log-level WARN \ + --onefile --windowed --name SaraSort --collect-data flet \ + --add-data "$PWD/assets:assets" \ + --distpath build/PyInstaller/dist --workpath build/PyInstaller/work --specpath build/PyInstaller \ + "$PWD/Main.py" + +AppDir=build/AppDir +rm -rf "$AppDir" +mkdir -p "$AppDir/usr/bin" +cp build/PyInstaller/dist/SaraSort "$AppDir/usr/bin/SaraSort" +ln -s usr/bin/SaraSort "$AppDir/AppRun" +cp assets/Icon.png "$AppDir/SaraSort.png" +ln -s SaraSort.png "$AppDir/.DirIcon" +cat > "$AppDir/SaraSort.desktop" << DESKTOP +[Desktop Entry] +Type=Application +Name=SaraSort +Comment=Configure SaraSortd, the file sorting daemon +Exec=SaraSort +Icon=SaraSort +Terminal=false +Categories=Utility; +DESKTOP + +mkdir -p dist +ARCH=x86_64 appimagetool --no-appstream "$AppDir" dist/SaraSort-x86_64.AppImage +echo "Built dist/SaraSort-x86_64.AppImage" diff --git a/Packaging/Launch.sh b/Packaging/Launch.sh new file mode 100755 index 0000000..514065b --- /dev/null +++ b/Packaging/Launch.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# Desktop-entry launcher: resolves the venv relative to this script rather +# than the caller's cwd, since .desktop Exec= lines are invoked with no +# guaranteed working directory. +cd "$(dirname "$(readlink -f "$0")")/.." || exit 1 +exec .venv/bin/python Main.py "$@" diff --git a/Packaging/SaraSuite/Manifest.toml b/Packaging/SaraSuite/Manifest.toml new file mode 100644 index 0000000..b02323d --- /dev/null +++ b/Packaging/SaraSuite/Manifest.toml @@ -0,0 +1,27 @@ +# Ready for SaraSuite: copy this folder to SaraSuite/Programs/SaraSort/ +# (Manifest.toml + Templates/). Same shape as SaraFem's manifest. +Name = "SaraSort" +Description = "A GUI for configuring SaraSortd: a flowchart of what goes where, its service and logs, configs and templates." + +[Source] +Git = "{GitHost}/SaraSort" +Include = ["*.py", "requirements.txt", "assets/**"] +Exclude = ["Packaging"] + +[Python] +RequirementsFile = "requirements.txt" + +[[Templates]] +Source = "Launch.sh" +Target = "{AppDir}/Packaging/Launch.sh" +Executable = true + +[[Templates]] +Source = "SaraSort.desktop" +Target = "{ApplicationsDir}/SaraSort.desktop" + +# Made by SaraSort itself: Config.toml, Layout.toml, Templates/. +[[Leftovers]] +Path = "{ConfigDir}" +Kind = "Config" +Note = "SaraSort's settings, flowchart layout and your templates" diff --git a/Packaging/SaraSuite/Templates/Launch.sh b/Packaging/SaraSuite/Templates/Launch.sh new file mode 100755 index 0000000..514065b --- /dev/null +++ b/Packaging/SaraSuite/Templates/Launch.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# Desktop-entry launcher: resolves the venv relative to this script rather +# than the caller's cwd, since .desktop Exec= lines are invoked with no +# guaranteed working directory. +cd "$(dirname "$(readlink -f "$0")")/.." || exit 1 +exec .venv/bin/python Main.py "$@" diff --git a/Packaging/SaraSuite/Templates/SaraSort.desktop b/Packaging/SaraSuite/Templates/SaraSort.desktop new file mode 100644 index 0000000..079c9a7 --- /dev/null +++ b/Packaging/SaraSuite/Templates/SaraSort.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Type=Application +Name=SaraSort +Comment=Configure SaraSortd, the file sorting daemon +Exec={AppDir}/Packaging/Launch.sh +Icon={AppDir}/assets/Icon.png +Terminal=false +Categories=Utility; +StartupNotify=true diff --git a/README.md b/README.md new file mode 100644 index 0000000..9ffaadb --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# SaraSort + +A GUI for configuring [SaraSortd](https://git.srhtnin.garden/sarah/SaraSortd), built with Flet. +SaraSort doesn't sort anything itself: SaraSortd stays the backend, and SaraSort reads and edits its +configs and controls its systemd user service. + +Needs SaraSortd **7.1** or newer for arrows with their own patterns (per-entry `ValidInputDirs`). + +## Pages + +- **Flow**: every input (red), output (blue) and both (purple) directory as a box, with arrows labelled + with the file patterns that travel along them. Click a label to see all of its patterns. Boxes start in + columns and can be dragged (positions are kept in `Layout.toml`). + In **Edit mode**: click a directory, then the one it should sort into, and type the patterns. Each + pattern becomes its own `[[Files]]` entry in the output's config, with its own `ValidInputDirs`, and + the other settings come from `OutputDirConf.toml` (the template). Click a label to add or remove + patterns, remove the arrow, or save it as a template. Click a box to remove it or open its config. +- **Service**: running/stopped, start/stop/restart, start on login, SaraSortd's version (and the + SaraSuite install it came from), and its logs: its own log files or the systemd journal. +- **Configs**: `GlobalConf.toml`, the two templates and every directory's own config, as a form (with + help text, a live NewFileName preview and variable buttons) or as raw TOML. Saving only writes the + values you changed, so SaraSortd's own NextNum/NextChar/LastFile updates aren't overwritten. +- **Templates**: ready-made connections (Pictures, Videos, Music, Documents, Archives, Git repositories, + Logs, 3D printing, Installers, Backup copy) that only ask for an input and an output. Your own templates + are TOML files in `~/.config/SaraSuite/SaraSort/Templates/`. +- **Tools**: warnings about the setup (Unset values, version mismatches, outputs nothing reaches, inputs + without outputs, entries shadowed by a catch-all), a filename tester that shows where a file would go + and why, and the files in FailedDir with a retry button. +- **Settings**: the shared SaraSuite appearance, SaraSort's own settings, where SaraSortd lives, and + export/import in the same file format as SaraFem's (`[SaraSuite]` plus `[SaraSort]`). + +After a change to `GlobalConf.toml` (new inputs or outputs) SaraSort restarts SaraSortd, since SaraSortd +only reads that file when it starts. That can be turned off in Settings. + +## Where things live + +| Path | What | +| --- | --- | +| `~/.config/SaraSuite/Config.toml` | shared SaraSuite settings (theme, font, title bar) | +| `~/.config/SaraSuite/Themes/` | the themes | +| `~/.config/SaraSuite/SaraSort/Config.toml` | SaraSort's own settings | +| `~/.config/SaraSuite/SaraSort/Layout.toml` | where flowchart boxes were dragged to | +| `~/.config/SaraSuite/SaraSort/Templates/` | your own templates | +| `~/.config/SaraSuite/SaraSortd/` | SaraSortd's configs, as SaraSuite installs them (changeable in Settings) | + +## Running and building + +- From source: `python3 -m venv .venv && .venv/bin/pip install -r requirements.txt`, then `Packaging/Launch.sh`. +- AppImage: `Packaging/BuildAppImage.sh` builds `dist/SaraSort-x86_64.AppImage` (PyInstaller one-file + build + `appimagetool`, like SaraSuite's and SaraFem's). +- SaraSuite: `Packaging/SaraSuite/` is a ready manifest (`Manifest.toml` + `Templates/`) to copy into + SaraSuite's `Programs/SaraSort/`. + +## Files + +| File | What | +| --- | --- | +| `Main.py` | window, navigation | +| `Core.py` | paths, palette/theme, settings, export/import | +| `Sortd.py` | SaraSortd's configs: the graph, edits, the filename simulator, warnings | +| `Service.py` | systemctl and journalctl | +| `Templates.py` | built-in and your own templates | +| `Widgets.py` | the shared App context and small controls | +| `*Page.py` | one per page | diff --git a/Service.py b/Service.py new file mode 100644 index 0000000..16bfcae --- /dev/null +++ b/Service.py @@ -0,0 +1,81 @@ +"""SaraSortd's systemd user unit: status, start/stop/restart, autostart, and +its journal. SaraSortd's own log files are read from its LogDir.""" + +import subprocess +from dataclasses import dataclass +from pathlib import Path + +from Core import ChildEnvironment + + +@dataclass +class ServiceStatus: + Active: str = "unknown" # active, inactive, failed, activating, ... + Sub: str = "" # running, dead, ... + Enabled: str = "unknown" # enabled, disabled, ... + Pid: str = "" + Since: str = "" + Found: bool = False + + @property + def Running(self) -> bool: + return self.Active in ("active", "activating", "reloading") + + +def _Run(Arguments: list[str], Timeout: int = 15) -> subprocess.CompletedProcess: + return subprocess.run(Arguments, capture_output=True, text=True, timeout=Timeout, env=ChildEnvironment()) + + +def Status(Unit: str) -> ServiceStatus: + try: + Result = _Run(["systemctl", "--user", "show", Unit, + "-p", "LoadState,ActiveState,SubState,UnitFileState,MainPID,ActiveEnterTimestamp"]) + except (OSError, subprocess.SubprocessError): + return ServiceStatus() + Values = dict(Line.split("=", 1) for Line in Result.stdout.splitlines() if "=" in Line) + return ServiceStatus( + Active=Values.get("ActiveState", "unknown"), + Sub=Values.get("SubState", ""), + Enabled=Values.get("UnitFileState", "") or "unknown", + Pid=Values.get("MainPID", "") if Values.get("MainPID") != "0" else "", + Since=Values.get("ActiveEnterTimestamp", ""), + Found=Values.get("LoadState") == "loaded", + ) + + +def Control(Unit: str, Action: str) -> str: + """Action: start, stop, restart, enable, disable. Returns "" or an error.""" + try: + Result = _Run(["systemctl", "--user", Action, Unit]) + except (OSError, subprocess.SubprocessError) as Error: + return str(Error) + return "" if Result.returncode == 0 else (Result.stderr.strip() or f"systemctl {Action} failed") + + +def RestartIfRunning(Unit: str) -> str: + """For after a GlobalConf.toml change: SaraSortd only reads it at start.""" + if not Status(Unit).Running: + return "" + return Control(Unit, "restart") + + +def Journal(Unit: str, Lines: int) -> list[str]: + try: + Result = _Run(["journalctl", "--user", "-u", Unit, "-n", str(Lines), "--no-pager", "-o", "short-iso"]) + except (OSError, subprocess.SubprocessError) as Error: + return [f"Couldn't read the journal: {Error}"] + return Result.stdout.splitlines() or [Result.stderr.strip() or "(empty)"] + + +def LogFiles(LogDir: Path) -> list[Path]: + """Newest first.""" + if not LogDir.is_dir(): + return [] + return sorted((Item for Item in LogDir.iterdir() if Item.is_file()), key=lambda Item: Item.stat().st_mtime, reverse=True) + + +def ReadTail(FilePath: Path, Lines: int) -> list[str]: + try: + return FilePath.read_text(encoding="utf-8", errors="replace").splitlines()[-Lines:] + except OSError as Error: + return [f"Couldn't read {FilePath}: {Error}"] diff --git a/ServicePage.py b/ServicePage.py new file mode 100644 index 0000000..56019cc --- /dev/null +++ b/ServicePage.py @@ -0,0 +1,222 @@ +"""SaraSortd's service: status, start/stop/restart, start on login, its +version, and its logs (its own log files and the systemd journal).""" + +import asyncio +from pathlib import Path + +import flet as ft +import tomlkit + +from Core import ChipButton, ChipButtonStyle, DataHome, Heading, Note, Palette +from Service import Control, Journal, LogFiles, ReadTail, Status +from Widgets import App + +ReceiptPath = DataHome / "SaraSuite" / "Receipts" / "SaraSortd.toml" + + +def LineColor(Line: str) -> str: + Lower = Line.lower() + if "error" in Lower or "traceback" in Lower or "failed" in Lower: + return Palette.Red + if "sorted" in Lower: + return Palette.Green + if "started" in Lower or "stopped" in Lower: + return Palette.Mauve + if "checking" in Lower: + return Palette.Overlay0 + return Palette.Text + + +class ServicePage(ft.Column): + def __init__(self, TheApp: App): + super().__init__(expand=True, spacing=0) + self.App = TheApp + self.Polling = False + self.Source = "Files" # or "Journal" + + self.Dot = ft.Container(width=14, height=14, border_radius=7, bgcolor=Palette.Overlay0) + self.StateText = ft.Text("", size=16) + self.Details = ft.Column(spacing=2) + self.VersionInfo = ft.Column(spacing=2) + self.Autostart = ft.Switch(label="Start SaraSortd on login", on_change=self._ToggleAutostart) + self.StartButton = ChipButton("Start", lambda e: self._Do("start"), ft.Icons.PLAY_ARROW) + self.StopButton = ChipButton("Stop", lambda e: self._Do("stop"), ft.Icons.STOP, Accent=Palette.Red) + self.RestartButton = ChipButton("Restart", lambda e: self._Do("restart"), ft.Icons.RESTART_ALT) + + self.FilesButton = ChipButton("Log files", lambda e: self._SetSource("Files")) + self.JournalButton = ChipButton("Journal", lambda e: self._SetSource("Journal")) + self.FilePicker = ft.Dropdown(label="Log file", dense=True, width=260, on_select=lambda e: self._LoadLog()) + self.HideChecking = ft.Checkbox(label="Hide \"Checking\" lines", value=True, on_change=lambda e: self._LoadLog()) + self.Follow = ft.Checkbox(label="Follow", value=True) + self.LogView = ft.ListView(expand=True, spacing=0, padding=8, auto_scroll=True) + + StatusCard = ft.Container( + ft.Column([ + ft.Row([self.Dot, self.StateText], spacing=10), + self.Details, + ft.Row([self.StartButton, self.StopButton, self.RestartButton], wrap=True), + self.Autostart, + ], spacing=10), + padding=14, bgcolor=Palette.Mantle, border=ft.Border.all(1, Palette.Surface1), border_radius=6, expand=True, + ) + VersionCard = ft.Container( + ft.Column([ft.Text("Version", color=Palette.Mauve), self.VersionInfo], spacing=8), + padding=14, bgcolor=Palette.Mantle, border=ft.Border.all(1, Palette.Surface1), border_radius=6, expand=True, + ) + self.controls = [ + ft.Container( + ft.Row([Heading("Service"), ft.Container(expand=True), + ChipButton("Reload", lambda e: self.Refresh(), ft.Icons.REFRESH)]), + padding=ft.Padding.only(left=16, right=16, top=12, bottom=8), + ), + ft.Container(ft.Row([StatusCard, VersionCard], vertical_alignment=ft.CrossAxisAlignment.START), + padding=ft.Padding.symmetric(horizontal=16)), + ft.Container( + ft.Row([self.FilesButton, self.JournalButton, self.FilePicker, ft.Container(width=8), + self.HideChecking, self.Follow], + vertical_alignment=ft.CrossAxisAlignment.CENTER, scroll=ft.ScrollMode.AUTO), + padding=ft.Padding.only(left=16, right=16, top=14, bottom=6), + ), + ft.Container(self.LogView, expand=True, bgcolor=Palette.Crust, margin=ft.Margin.only(left=16, right=16, bottom=16), + border=ft.Border.all(1, Palette.Surface1), border_radius=6), + ] + + # Status + + def Refresh(self) -> None: + self._RefreshStatus() + self._RefreshVersion() + self._RefreshLogFiles() + self._LoadLog() + self._StyleSourceButtons() + self.update() + + def _RefreshStatus(self) -> None: + Current = Status(self.App.Config.ServiceName) + if not Current.Found: + Color, Text = Palette.Overlay0, f"{self.App.Config.ServiceName} isn't installed" + elif Current.Running: + Color, Text = Palette.Green, f"Running ({Current.Sub})" + elif Current.Active == "failed": + Color, Text = Palette.Red, "Failed" + else: + Color, Text = Palette.Yellow, f"Stopped ({Current.Active})" + self.Dot.bgcolor = Color + self.StateText.value = Text + self.StateText.color = Color + self.Details.controls = [ + Note(f"Unit: {self.App.Config.ServiceName}"), + Note(f"Since: {Current.Since or '-'}"), + Note(f"PID: {Current.Pid or '-'}"), + Note(f"Unit file: {Current.Enabled}"), + ] + self.StartButton.disabled = Current.Running or not Current.Found + self.StopButton.disabled = not Current.Running + self.RestartButton.disabled = not Current.Found + self.Autostart.value = Current.Enabled == "enabled" + self.Autostart.disabled = not Current.Found + + def _RefreshVersion(self) -> None: + TheSetup, Error = self.App.LoadSetup() + Lines = [] + Daemon = TheSetup.DaemonVersion() if TheSetup else None + Lines.append(ft.Text(f"SaraSortd {Daemon or '?'}", size=16)) + Lines.append(Note(str(self.App.Config.SortdScriptPath))) + if TheSetup: + ConfVersion = str(TheSetup.Data.get("Version", "?")) + Mismatch = Daemon and ConfVersion.split(".")[0] != Daemon.split(".")[0] + Lines.append(ft.Text(f"GlobalConf.toml {ConfVersion}" + (" (major version differs!)" if Mismatch else ""), + color=Palette.Red if Mismatch else Palette.Text, size=13)) + else: + Lines.append(ft.Text(Error, color=Palette.Red, size=12)) + try: + Receipt = tomlkit.parse(ReceiptPath.read_text()).unwrap() + Lines.append(Note(f"Installed by SaraSuite: commit {str(Receipt.get('Commit', '?'))[:10]}")) + Lines.append(Note(f"Installed {Receipt.get('InstalledAt', '?')}, updated {Receipt.get('UpdatedAt', '?')}")) + except (OSError, tomlkit.exceptions.TOMLKitError): + Lines.append(Note("Not installed through SaraSuite (no receipt).")) + self.VersionInfo.controls = Lines + + def _Do(self, Action: str) -> None: + Error = Control(self.App.Config.ServiceName, Action) + if Error: + self.App.Notify(Error, "error") + else: + self.App.Notify(f"SaraSortd: {Action} done.", "success") + self.Refresh() + + def _ToggleAutostart(self, e) -> None: + Error = Control(self.App.Config.ServiceName, "enable" if self.Autostart.value else "disable") + if Error: + self.App.Notify(Error, "error") + self._RefreshStatus() + self.update() + + # Logs + + def _StyleSourceButtons(self) -> None: + self.FilesButton.style = ChipButtonStyle(Palette.Mauve if self.Source == "Files" else Palette.Surface1) + self.JournalButton.style = ChipButtonStyle(Palette.Mauve if self.Source == "Journal" else Palette.Surface1) + self.FilePicker.visible = self.Source == "Files" + + def _SetSource(self, Source: str) -> None: + self.Source = Source + self._StyleSourceButtons() + self._LoadLog() + self.update() + + def _LogDir(self) -> Path | None: + TheSetup, _ = self.App.LoadSetup() + return TheSetup.SpecialDir("LogDir") if TheSetup else None + + def _RefreshLogFiles(self) -> None: + LogDir = self._LogDir() + Files = LogFiles(LogDir) if LogDir else [] + Current = self.FilePicker.value + self.FilePicker.options = [ft.DropdownOption(key=str(FilePath), text=FilePath.name) for FilePath in Files] + Keys = [str(FilePath) for FilePath in Files] + if Current not in Keys: + self.FilePicker.value = Keys[0] if Keys else None + + def _LoadLog(self) -> None: + Limit = self.App.Config.LogLines + if self.Source == "Journal": + Lines = Journal(self.App.Config.ServiceName, Limit) + elif self.FilePicker.value: + Lines = ReadTail(Path(self.FilePicker.value), Limit * 4) + else: + Lines = ["No log files yet (is Logging = 1 in GlobalConf.toml?)."] + if self.HideChecking.value: + Lines = [Line for Line in Lines if "Checking" not in Line] + Lines = Lines[-Limit:] + self.LogView.auto_scroll = bool(self.Follow.value) + self.LogView.controls = [ + ft.Text(Line, size=12, color=LineColor(Line), selectable=True) for Line in Lines + ] + if self.page: + self.LogView.update() + + # Polling while the page is shown + + def StartPolling(self) -> None: + if self.Polling: + return + self.Polling = True + self.App.Page.run_task(self._Poll) + + def StopPolling(self) -> None: + self.Polling = False + + async def _Poll(self) -> None: + while self.Polling: + await asyncio.sleep(max(1, int(self.App.Config.RefreshSeconds))) + if not self.Polling: + break + try: + self._RefreshStatus() + if self.Follow.value: + self._RefreshLogFiles() + self._LoadLog() + self.update() + except Exception: # the page may be gone mid-update + pass diff --git a/SettingsPage.py b/SettingsPage.py new file mode 100644 index 0000000..58c337a --- /dev/null +++ b/SettingsPage.py @@ -0,0 +1,196 @@ +"""SaraSort's own settings (not SaraSortd's): the shared SaraSuite appearance, +SaraSort's own, where SaraSortd lives, and exporting/importing settings in +the same file format SaraFem uses ([SaraSuite] + one section per program).""" + +import subprocess +from pathlib import Path +from typing import Callable + +import flet as ft +import tomlkit + +from Core import ( + AppConfig, AvailableThemes, BundledFontFamily, ChildEnvironment, ChipButton, Heading, LoadSharedSettings, Note, + Palette, SharedSettings, ExportSettings, ImportSettings, +) +from Widgets import App + +CategoryListWidth = 170 +RestartNote = "Theme, font and title bar changes apply the next time SaraSort starts." +Pages = ["Flow", "Service", "Configs", "Templates", "Tools", "Settings"] + + +def InstalledFonts() -> list[str]: + try: + Result = subprocess.run(["fc-list", ":", "family"], capture_output=True, text=True, timeout=5, env=ChildEnvironment()) + Families = {Line.split(",")[0].strip() for Line in Result.stdout.splitlines() if Line.strip()} + except (OSError, subprocess.SubprocessError): + Families = set() + Families.discard(BundledFontFamily) + return [BundledFontFamily] + sorted(Families, key=str.lower) + + +class SettingsPage(ft.Column): + def __init__(self, TheApp: App, OnSaved: Callable[[], None]): + super().__init__(expand=True, spacing=0) + self.App = TheApp + self.OnSaved = OnSaved + self.Selected = "Shared" + self.Detail = ft.Container(expand=True, padding=ft.Padding.only(left=16, right=16, top=8)) + self.CategoryList = ft.Column(spacing=0) + self.Fonts: list[str] = [] + self.controls = [ + ft.Container( + ft.Row([ + Heading("Settings"), ft.Container(expand=True), + ChipButton("Import", self._Import, ft.Icons.FILE_DOWNLOAD_OUTLINED), + ChipButton("Export", self._Export, ft.Icons.FILE_UPLOAD_OUTLINED), + ChipButton("Revert", lambda e: self.Refresh(), ft.Icons.UNDO), + ChipButton("Save", lambda e: self._Save(), ft.Icons.SAVE_OUTLINED), + ]), + padding=ft.Padding.only(left=16, right=16, top=12, bottom=8), + ), + ft.Divider(height=1, color=Palette.Surface1), + ft.Row([ + ft.Container(self.CategoryList, width=CategoryListWidth, bgcolor=Palette.Mantle, padding=4), + self.Detail, + ], expand=True, vertical_alignment=ft.CrossAxisAlignment.STRETCH), + ft.Container(Note(RestartNote), padding=ft.Padding.only(left=16, bottom=10, top=6)), + ] + + def Refresh(self) -> None: + if not self.Fonts: + self.Fonts = InstalledFonts() + Config = self.App.Config + Themes = AvailableThemes() + + def ThemePicker(Value: str) -> ft.Dropdown: + Names = Themes if Value in Themes else [Value] + Themes + return ft.Dropdown(value=Value, label="Theme", options=[ft.DropdownOption(key=Name, text=Name) for Name in Names]) + + def FontPicker(Value: str) -> ft.Dropdown: + Names = self.Fonts if Value in self.Fonts else [Value] + self.Fonts + return ft.Dropdown(value=Value, label="Font", editable=True, enable_filter=True, menu_height=320, + options=[ft.DropdownOption(key=Name, text=Name) for Name in Names]) + + Shared = LoadSharedSettings(Config) + self.UseShared = ft.Switch(label="SaraSort uses these shared settings", value=Config.UseSharedSettings, + on_change=lambda e: self._DimOwn()) + self.SharedTheme = ThemePicker(Shared.Theme) + self.SharedFont = FontPicker(Shared.FontFamily) + self.SharedTitleBar = ft.Switch(label="Hide window title bar", value=Shared.HideWindowTitleBar) + + self.OwnTheme = ThemePicker(Config.Theme) + self.OwnFont = FontPicker(Config.FontFamily) + self.OwnTitleBar = ft.Switch(label="Hide window title bar", value=Config.HideWindowTitleBar) + self.OwnAppearance = ft.Column([ + Note("SaraSort's own appearance, used when shared settings are off"), + self.OwnTheme, self.OwnFont, self.OwnTitleBar, + ], spacing=12) + + self.ConfigDir = ft.TextField(label="SaraSortd config folder (GlobalConf.toml and the templates)", value=Config.SortdConfigDir) + self.AppDir = ft.TextField(label="SaraSortd program folder (SaraSortd.py)", value=Config.SortdAppDir) + self.Unit = ft.TextField(label="systemd user unit", value=Config.ServiceName) + self.AutoRestart = ft.Switch(label="Restart SaraSortd after changes to GlobalConf.toml", value=Config.AutoRestart) + self.Refresh_ = ft.TextField(label="Service page refresh (seconds)", value=str(Config.RefreshSeconds), width=260, + keyboard_type=ft.KeyboardType.NUMBER) + self.LogLines = ft.TextField(label="Log lines to show", value=str(Config.LogLines), width=260, + keyboard_type=ft.KeyboardType.NUMBER) + self.LabelLength = ft.TextField(label="Arrow label length before shortening", value=str(Config.LabelLength), width=260, + keyboard_type=ft.KeyboardType.NUMBER) + self.StartPage = ft.Dropdown(label="Start page", value=Config.StartPage, width=260, + options=[ft.DropdownOption(key=Name, text=Name) for Name in Pages[:-1]]) + + self.Categories = { + "Shared": ft.Column([ + Note("Shared by every SaraSuite program (~/.config/SaraSuite/Config.toml)."), + self.UseShared, + ft.Divider(height=1, color=Palette.Surface1), + self.SharedTheme, self.SharedFont, self.SharedTitleBar, + ], spacing=12, scroll=ft.ScrollMode.AUTO), + "SaraSort": ft.Column([ + self.StartPage, self.LabelLength, + ft.Divider(height=1, color=Palette.Surface1), + self.OwnAppearance, + ], spacing=12, scroll=ft.ScrollMode.AUTO), + "SaraSortd": ft.Column([ + Note("Defaults are where SaraSuite installs SaraSortd."), + self.ConfigDir, self.AppDir, self.Unit, self.AutoRestart, self.Refresh_, self.LogLines, + ], spacing=12, scroll=ft.ScrollMode.AUTO), + } + self._DimOwn(Update=False) + self._Select(self.Selected, Update=False) + self.update() + + def _DimOwn(self, Update: bool = True) -> None: + self.OwnAppearance.disabled = bool(self.UseShared.value) + self.OwnAppearance.opacity = 0.5 if self.UseShared.value else 1 + if Update: + self.OwnAppearance.update() + + def _Select(self, Name: str, Update: bool = True) -> None: + self.Selected = Name + self.CategoryList.controls = [ + ft.ListTile(title=Category, selected=Category == Name, selected_tile_color=Palette.Surface1, + selected_color=Palette.Mauve, on_click=lambda e, Category=Category: self._Select(Category)) + for Category in self.Categories + ] + self.Detail.content = self.Categories[Name] + if Update: + self.update() + + @staticmethod + def _Int(Field: ft.TextField, Fallback: int, Minimum: int) -> int: + try: + return max(Minimum, int(Field.value)) + except (TypeError, ValueError): + return Fallback + + def _Save(self) -> None: + Config: AppConfig = self.App.Config + Config.UseSharedSettings = bool(self.UseShared.value) + Config.Theme = self.OwnTheme.value or Config.Theme + Config.FontFamily = self.OwnFont.value or Config.FontFamily + Config.HideWindowTitleBar = bool(self.OwnTitleBar.value) + Config.SortdConfigDir = (self.ConfigDir.value or "").strip() or Config.SortdConfigDir + Config.SortdAppDir = (self.AppDir.value or "").strip() or Config.SortdAppDir + Config.ServiceName = (self.Unit.value or "").strip() or Config.ServiceName + Config.AutoRestart = bool(self.AutoRestart.value) + Config.RefreshSeconds = self._Int(self.Refresh_, Config.RefreshSeconds, 1) + Config.LogLines = self._Int(self.LogLines, Config.LogLines, 10) + Config.LabelLength = self._Int(self.LabelLength, Config.LabelLength, 6) + Config.StartPage = self.StartPage.value or Config.StartPage + Config.Save() + Shared = LoadSharedSettings(Config) + SharedSettings( + Theme=self.SharedTheme.value or Shared.Theme, + FontFamily=self.SharedFont.value or Shared.FontFamily, + HideWindowTitleBar=bool(self.SharedTitleBar.value), + ).Save() + self.App.Notify("Settings saved.", "success") + self.OnSaved() + + async def _Export(self, e) -> None: + Chosen = await self.App.Picker.save_file(dialog_title="Export settings", file_name="SaraSortSettings.toml", + allowed_extensions=["toml"]) + if not Chosen: + return + try: + ExportSettings(self.App.Config, Path(Chosen)) + except OSError as Error: + self.App.Notify(f"Couldn't export settings: {Error}", "error") + return + self.App.Notify("Settings exported.", "success") + + async def _Import(self, e) -> None: + Files = await self.App.Picker.pick_files(dialog_title="Import settings", allowed_extensions=["toml"]) + if not Files: + return + try: + ImportSettings(self.App.Config, Path(Files[0].path)) + except (OSError, tomlkit.exceptions.TOMLKitError) as Error: + self.App.Notify(f"Couldn't import settings: {Error}", "error") + return + self.Refresh() + self.App.Notify("Settings imported. Theme, font and title bar apply on the next start.", "success") + self.OnSaved() diff --git a/Sortd.py b/Sortd.py new file mode 100644 index 0000000..f2a4ff5 --- /dev/null +++ b/Sortd.py @@ -0,0 +1,673 @@ +"""SaraSortd's configs as SaraSort sees them: which directories are inputs and +outputs, which file patterns go from where to where, and the edits the +flowchart and templates make. + +SaraSort never sorts anything itself. To show what SaraSortd will really do, +Parse, GetConf and Simulate follow SaraSortd.py's own Parse, GetConf and +DecideNewPath, quirks included (e.g. Parse also swaps variable characters +inside paths). All writes go through tomlkit, so comments and layout in the +files survive; SaraSortd itself rewrites directory configs with `toml`, which +drops comments, but GlobalConf.toml and the templates keep theirs. +""" + +import datetime +import fnmatch +import os +import re +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any, Optional + +import tomlkit +from tomlkit.items import AoT + +from Core import AppConfig + +TemplateNames = {"In": "InputDirConf.toml", "Out": "OutputDirConf.toml"} +NameKeys = {"In": "InDirConfName", "Out": "OutDirConfName"} +# 0/1 switches in SaraSortd's configs, shown as switches in the editor. +SwitchKeys = {"SafeMode", "SilentMode", "DotFiles", "Logging", "OverwriteDirConf", "DeleteOrg", "SortDirs", "CaseSensitive", "Overwrite"} + + +class SortdError(Exception): + """GlobalConf.toml is missing or unreadable.""" + + +def ReadToml(FilePath: Path) -> tomlkit.TOMLDocument: + return tomlkit.parse(FilePath.read_text(encoding="utf-8")) + + +def WriteToml(FilePath: Path, Document: tomlkit.TOMLDocument) -> None: + FilePath.write_text(tomlkit.dumps(Document), encoding="utf-8") + + +def GetConf(Parameter: str, Data: dict) -> Any: + """SaraSortd's GetConf: a top-level key, or one a level down inside a + table or array of tables.""" + if Parameter in Data: + return Data[Parameter] + for Value in Data.values(): + if isinstance(Value, dict): + if Parameter in Value: + return Value[Parameter] + elif isinstance(Value, list): + for Item in Value: + if isinstance(Item, dict) and Parameter in Item: + return Item[Parameter] + return None + + +def MajorVersion(Version: str) -> str: + return str(Version).split(".")[0] + + +def VersionAtLeast(Version: str, Wanted: tuple[int, int]) -> bool: + try: + Parts = tuple(int(Part) for Part in str(Version).split(".")[:2]) + except ValueError: + return False + return Parts >= Wanted + + +@dataclass +class FileRule: + """One [[Files]] entry of an output directory that applies to an arrow.""" + + Index: int # position in the output's Files array + Pattern: str + NewFileName: str + OwnInputs: bool # True: the entry has its own ValidInputDirs (7.1) + + +@dataclass +class Edge: + Input: str + Output: str + Rules: list[FileRule] = field(default_factory=list) + + @property + def Patterns(self) -> list[str]: + return [Rule.Pattern for Rule in self.Rules] + + +@dataclass +class DirNode: + Path: str # resolved, as SaraSortd sees it + IsInput: bool = False + IsOutput: bool = False + + @property + def Kind(self) -> str: + if self.IsInput and self.IsOutput: + return "Both" + return "Input" if self.IsInput else "Output" + + +@dataclass +class SimulationResult: + Steps: list[str] + Destination: Optional[str] # full new path, or None + Outcome: str # "Sorted", "Failed", "Skipped" + + +class Setup: + """GlobalConf.toml plus everything reachable from it. Cheap to reload: + pages just make a new one after each change.""" + + def __init__(self, Config: AppConfig): + self.Config = Config + self.ConfDir = Path(Config.SortdConfigDir).expanduser() + self.GlobalPath = Config.GlobalConfPath + if not self.GlobalPath.exists(): + raise SortdError(f"No GlobalConf.toml at {self.GlobalPath}") + try: + self.Global = ReadToml(self.GlobalPath) + except tomlkit.exceptions.TOMLKitError as Error: + raise SortdError(f"GlobalConf.toml can't be read: {Error}") from Error + self.Data = self.Global.unwrap() + self.Vars = self.Data.get("Variables", {}) + self.Dirs = self.Data.get("DirectoryPaths", {}) + self.Names = self.Data.get("Names", {}) + + # Parsing, as SaraSortd.py does it + + def Parse( + self, String: str, ConfData: Optional[dict] = None, NextNum=None, NextChar=None, Parent=None, + OrgFile: Optional[str] = None, VarCall=None, Now: Optional[datetime.datetime] = None, + ) -> str: + """SaraSortd's Parse. ConfData is the file %Name% lookups read from + (GlobalConf unless given, like SaraSortd's Path argument).""" + if not isinstance(String, str): + return String + ConfData = self.Data if ConfData is None else ConfData + Now = Now or datetime.datetime.now() + OrgFileName, OrgFileType = os.path.splitext(OrgFile) if OrgFile is not None else (None, None) + V = self.Vars + VarValues = { + V.get("NextNum"): str(NextNum), + V.get("NextChar"): str(NextChar), + V.get("Parent"): str(Parent), + V.get("OrgFileName"): str(OrgFileName), + V.get("OrgFileType"): str(OrgFileType), + V.get("Year"): str(Now.year), + V.get("Month"): str(Now.month).rjust(2, "0"), + V.get("Day"): str(Now.day).rjust(2, "0"), + V.get("Hour"): str(Now.hour).rjust(2, "0"), + V.get("Minute"): str(Now.minute).rjust(2, "0"), + V.get("Second"): str(Now.second).rjust(2, "0"), + V.get("VarCall"): str(VarCall), + } + for Key, Value in VarValues.items(): + if Key and Key in String: + String = String.replace(Key, Value) + + Call = V.get("VarCall", "%%") + if len(Call) < 2: + return String + + def Replacer(Match: re.Match) -> str: + Content = Match.group(1) + if not Content: + return str(VarCall) if VarCall is not None else "" + Found = GetConf(Content, ConfData) + return str(Found) if Found is not None else Match.group(0) + + return re.sub(f"{re.escape(Call[0])}(.*?){re.escape(Call[1])}", Replacer, String) + + def ResolveDir(self, Raw: str) -> str: + return self.Parse(Raw) + + def DirList(self, Key: str) -> list[str]: + Value = self.Dirs.get(Key, []) + return [str(Item) for Item in (Value if isinstance(Value, list) else [Value])] + + @property + def Inputs(self) -> list[str]: + return [self.ResolveDir(Raw) for Raw in self.DirList("InputDir")] + + @property + def Outputs(self) -> list[str]: + return [self.ResolveDir(Raw) for Raw in self.DirList("OutputDir")] + + def SpecialDir(self, Key: str) -> Optional[Path]: + Raw = self.Dirs.get(Key) + return Path(self.ResolveDir(str(Raw))) if Raw else None + + def RootDir(self) -> Optional[str]: + Root = self.Dirs.get("RootDir") + return None if not Root or Root == "Unset" else str(Root) + + def Shorthand(self, PathText: str) -> str: + """How a path is written into GlobalConf: %RootDir%/... when under RootDir.""" + Root = self.RootDir() + Call = self.Vars.get("VarCall", "%%") + if Root and len(Call) >= 2: + Root = Root.rstrip("/") + if PathText == Root or PathText.startswith(Root + "/"): + return f"{Call[0]}RootDir{Call[1]}{PathText[len(Root):]}" + return PathText + + def ConfFile(self, DirPath: str, Kind: str) -> Path: + """A directory's own config, e.g. ~/Sorting/Output/.OutputConfig.toml.""" + Name = self.Parse(str(self.Names.get(NameKeys[Kind], ".@Config")), Parent=os.path.basename(DirPath)) + return Path(DirPath) / f"{Name}.toml" + + def SharedConfName(self) -> bool: + """Input and output configs use the same file name (both default to .@Config).""" + return self.Names.get("InDirConfName") == self.Names.get("OutDirConfName") + + def TemplatePath(self, Kind: str) -> Path: + return self.ConfDir / TemplateNames[Kind] + + def LoadDirConf(self, DirPath: str, Kind: str, UseTemplate: bool = True) -> Optional[dict]: + """A directory's config. When it has none yet, the template it will + get from SaraSortd on its next start (unless UseTemplate is off).""" + ConfPath = self.ConfFile(DirPath, Kind) + try: + return ReadToml(ConfPath).unwrap() + except (OSError, tomlkit.exceptions.TOMLKitError): + pass + if not UseTemplate or ConfPath.exists(): + return None + try: + return self._NewDirConf(DirPath, Kind).unwrap() + except (OSError, tomlkit.exceptions.TOMLKitError): + return None + + def DaemonVersion(self) -> Optional[str]: + try: + Match = re.search(r'# Version = "([^"]+)"', self.Config.SortdScriptPath.read_text()) + except OSError: + return None + return Match.group(1) if Match else None + + # The graph + + def Nodes(self) -> dict[str, DirNode]: + Nodes: dict[str, DirNode] = {} + for DirPath in self.Inputs: + Nodes.setdefault(DirPath, DirNode(DirPath)).IsInput = True + for DirPath in self.Outputs: + Nodes.setdefault(DirPath, DirNode(DirPath)).IsOutput = True + return Nodes + + def _DirAccepts(self, OutConf: dict, OutputPath: str, InputPath: str) -> bool: + """The output directory's own ValidInputDirs, as DecideNewPath checks it.""" + Parent = GetConf("ParentDir", OutConf) + for Valid in GetConf("ValidInputDirs", OutConf) or []: + if fnmatch.fnmatchcase(InputPath, self.Parse(str(Valid), Parent=Parent)): + return True + return False + + def _EntryAccepts(self, Entry: dict, OutputPath: str, InputPath: str) -> bool: + """SaraSortd 7.1's FileInputValid.""" + Own = Entry.get("ValidInputDirs") + if Own is None: + return True + Parent = os.path.basename(OutputPath) + return any(fnmatch.fnmatchcase(InputPath, self.Parse(str(Valid), Parent=Parent)) for Valid in Own) + + def Edges(self) -> list[Edge]: + Edges = [] + for OutputPath in dict.fromkeys(self.Outputs): + OutConf = self.LoadDirConf(OutputPath, "Out") + if OutConf is None: + continue + Entries = OutConf.get("Files", []) + for InputPath in dict.fromkeys(self.Inputs): + if not self._DirAccepts(OutConf, OutputPath, InputPath): + continue + Found = Edge(InputPath, OutputPath) + for Index, Entry in enumerate(Entries): + if "Pattern" not in Entry or not self._EntryAccepts(Entry, OutputPath, InputPath): + continue + Found.Rules.append(FileRule( + Index, str(Entry["Pattern"]), str(Entry.get("NewFileName", "")), "ValidInputDirs" in Entry, + )) + if Found.Rules: + Edges.append(Found) + return Edges + + # Edits + + def _SaveGlobal(self) -> None: + WriteToml(self.GlobalPath, self.Global) + self.Data = self.Global.unwrap() + self.Dirs = self.Data.get("DirectoryPaths", {}) + + def _AddToList(self, Key: str, PathText: str) -> bool: + """Adds a directory to InputDir/OutputDir. True if GlobalConf changed.""" + if PathText in (self.Inputs if Key == "InputDir" else self.Outputs): + return False + Section = self.Global.setdefault("DirectoryPaths", tomlkit.table()) + Current = Section.get(Key) + if Current is None: + Section[Key] = [self.Shorthand(PathText)] + elif isinstance(Current, list): + Current.append(self.Shorthand(PathText)) + else: # a single string; SaraSortd expects a list + Section[Key] = [str(Current), self.Shorthand(PathText)] + self._SaveGlobal() + return True + + def AddDirectory(self, PathText: str, Keys: list[str]) -> bool: + """Adds a directory as an input and/or output ("InputDir"/"OutputDir") + and writes its config from the template. True if GlobalConf changed.""" + Changed = False + for Key in Keys: + Changed = self._AddToList(Key, PathText) or Changed + for Key in Keys: + Kind = "In" if Key == "InputDir" else "Out" + Document, IsNew = self.EnsureDirConf(PathText, Kind) + WriteToml(self.ConfFile(PathText, Kind), Document) + return Changed + + def RemoveDir(self, PathText: str) -> bool: + """Takes a directory out of InputDir and OutputDir. Leaves the + directory and its config on disk. True if GlobalConf changed.""" + Changed = False + Section = self.Global.get("DirectoryPaths", {}) + for Key in ("InputDir", "OutputDir"): + Current = Section.get(Key) + if not isinstance(Current, list): + continue + for Index in reversed(range(len(Current))): + if self.ResolveDir(str(Current[Index])) == PathText: + del Current[Index] + Changed = True + if Changed: + self._SaveGlobal() + return Changed + + def _NewDirConf(self, DirPath: str, Kind: str) -> tomlkit.TOMLDocument: + """What SaraSortd's Dir() would write: the template, with ParentDir and + Title set.""" + Name = os.path.basename(DirPath) + Document = ReadToml(self.TemplatePath(Kind)) + Document["ParentDir"] = Name + Document["Title"] = f"{Name} Config" + return Document + + def EnsureDirConf(self, DirPath: str, Kind: str) -> tuple[tomlkit.TOMLDocument, bool]: + """The directory's config, made from the template when missing. + Returns (document, IsNew). The directory itself is made too.""" + Path(DirPath).mkdir(parents=True, exist_ok=True) + ConfPath = self.ConfFile(DirPath, Kind) + if ConfPath.exists(): + Document = ReadToml(ConfPath) + IsNew = False + else: + Document = self._NewDirConf(DirPath, Kind) + IsNew = True + if self.SharedConfName() and DirPath in self.Inputs and DirPath in self.Outputs: + # One file for both roles: make sure it has the keys both read. + Other = "In" if Kind == "Out" else "Out" + OtherTemplate = ReadToml(self.TemplatePath(Other)) + for Key, Value in OtherTemplate.items(): + if Key != "Files" and Key not in Document: + Document[Key] = Value + # Its [[Files]] are read as output rules too, which need more keys. + Defaults = self.TemplateFileDefaults() + for Entry in Document.get("Files", []): + for Key, Value in Defaults.items(): + if Key not in Entry: + Entry[Key] = Value + return Document, IsNew + + def TemplateFileDefaults(self) -> dict: + """The first [[Files]] entry of OutputDirConf.toml: the defaults for + every new entry (NewFileName, NextNum, ...).""" + try: + Entries = ReadToml(self.TemplatePath("Out")).unwrap().get("Files", []) + except (OSError, tomlkit.exceptions.TOMLKitError): + Entries = [] + Defaults = dict(Entries[0]) if Entries else {} + Defaults.setdefault("NewFileName", "~^") + Defaults.setdefault("NextNum", "1") + Defaults.setdefault("NextChar", "A") + Defaults.setdefault("CaseSensitive", 0) + Defaults.setdefault("Overwrite", 1) + Defaults.pop("ValidInputDirs", None) + return Defaults + + def Connect( + self, InputPath: str, OutputPath: str, Patterns: list[str], + EntryOverrides: Optional[dict] = None, OutputSettings: Optional[dict] = None, + InputSettings: Optional[dict] = None, + ) -> bool: + """Makes InputPath send Patterns to OutputPath. Each pattern becomes a + [[Files]] entry in the output's config (defaults from OutputDirConf.toml) + with its own ValidInputDirs, so other inputs aren't affected. Returns + True if GlobalConf.toml changed (SaraSortd needs a restart).""" + Patterns = [Pattern.strip() for Pattern in Patterns if Pattern.strip()] + Changed = self._AddToList("InputDir", InputPath) + Changed = self._AddToList("OutputDir", OutputPath) or Changed + + # The output side. + OutDoc, IsNew = self.EnsureDirConf(OutputPath, "Out") + if IsNew and "Files" in OutDoc: + # The template's catch-all entry would take every file from every + # input; the new entries replace it. + del OutDoc["Files"] + for Key, Value in (OutputSettings or {}).items(): + OutDoc[Key] = Value + OutData = OutDoc.unwrap() + if not self._DirAccepts(OutData, OutputPath, InputPath): + Valid = OutDoc.get("ValidInputDirs") + if isinstance(Valid, list): + Valid.append(self.Shorthand(InputPath)) + else: + OutDoc["ValidInputDirs"] = [self.Shorthand(InputPath)] + if "Files" not in OutDoc: + OutDoc["Files"] = AoT([]) + Files = OutDoc["Files"] + Defaults = self.TemplateFileDefaults() + for Pattern in Patterns: + Existing = next((Entry for Entry in Files if Entry.get("Pattern") == Pattern), None) + if Existing is not None: + Own = Existing.get("ValidInputDirs") + if Own is not None and not self._EntryAccepts(Existing.unwrap(), OutputPath, InputPath): + Own.append(self.Shorthand(InputPath)) + for Key, Value in (EntryOverrides or {}).items(): + Existing[Key] = Value + continue + Entry = tomlkit.table() + Entry["Pattern"] = Pattern + for Key, Value in Defaults.items(): + if Key != "Pattern": + Entry[Key] = Value + for Key, Value in (EntryOverrides or {}).items(): + Entry[Key] = Value + Entry["ValidInputDirs"] = [self.Shorthand(InputPath)] + Files.append(Entry) + WriteToml(self.ConfFile(OutputPath, "Out"), OutDoc) + + # The input side: it has to let these files through at all. + InDoc, IsNew = self.EnsureDirConf(InputPath, "In") + for Key, Value in (InputSettings or {}).items(): + InDoc[Key] = Value + if "Files" not in InDoc: + InDoc["Files"] = AoT([]) + InPatterns = [str(Entry.get("Pattern")) for Entry in InDoc["Files"]] + # When the input is also an output and both use one config file, its + # [[Files]] are output rules too: an input-only entry (no NewFileName) + # would crash SaraSortd, so leave them alone. + SharedFile = self.SharedConfName() and InputPath in self.Outputs + if "*" not in InPatterns and not SharedFile: + for Pattern in Patterns: + if Pattern not in InPatterns: + Entry = tomlkit.table() + Entry["Pattern"] = Pattern + Entry["CaseSensitive"] = 0 + InDoc["Files"].append(Entry) + WriteToml(self.ConfFile(InputPath, "In"), InDoc) + return Changed + + def ConnectedInputs(self, OutputPath: str) -> list[str]: + OutConf = self.LoadDirConf(OutputPath, "Out") or {} + return [Input for Input in dict.fromkeys(self.Inputs) if self._DirAccepts(OutConf, OutputPath, Input)] + + def RemoveRule(self, InputPath: str, OutputPath: str, Index: int) -> Optional[str]: + """Stops the [[Files]] entry at Index taking files from InputPath, and + removes the entry once no input is left. Returns a message when that + can't be done exactly (a wildcard ValidInputDirs entry).""" + ConfPath = self.ConfFile(OutputPath, "Out") + Document = ReadToml(ConfPath) + Files = Document.get("Files") + if Files is None or Index >= len(Files): + return "That rule no longer exists." + Entry = Files[Index] + Own = Entry.get("ValidInputDirs") + Parent = os.path.basename(OutputPath) + if Own is None: + # It took files from every input: list the others explicitly. + Others = [Other for Other in self.ConnectedInputs(OutputPath) if Other != InputPath] + Entry["ValidInputDirs"] = [self.Shorthand(Other) for Other in Others] + Own = Entry["ValidInputDirs"] + else: + for Position in reversed(range(len(Own))): + if self.Parse(str(Own[Position]), Parent=Parent) == InputPath: + del Own[Position] + Message = None + if self._EntryAccepts(Entry.unwrap(), OutputPath, InputPath): + Message = f"{Entry.get('Pattern')} still matches through a wildcard in its ValidInputDirs; edit it in Configs." + if len(Own) == 0: + del Files[Index] + WriteToml(ConfPath, Document) + return Message + + def Disconnect(self, InputPath: str, OutputPath: str) -> list[str]: + Messages = [] + for FoundEdge in self.Edges(): + if FoundEdge.Input == InputPath and FoundEdge.Output == OutputPath: + for Rule in sorted(FoundEdge.Rules, key=lambda Rule: Rule.Index, reverse=True): + Message = self.RemoveRule(InputPath, OutputPath, Rule.Index) + if Message: + Messages.append(Message) + return Messages + + # What would happen to a file + + def Simulate(self, InputPath: str, FileName: str) -> SimulationResult: + """Follows Main() and DecideNewPath() for one file, without touching + anything (NextNum/NextChar aren't incremented).""" + Steps: list[str] = [] + if FileName.startswith(".") and self.Data.get("DotFiles", 0) == 0: + return SimulationResult(["Dot files are skipped (DotFiles = 0)."], None, "Skipped") + InConf = self.LoadDirConf(InputPath, "In") + if InConf is None: + return SimulationResult(["The input's config can't be read."], None, "Skipped") + Parent = os.path.basename(InputPath) + Matched = False + for Entry in InConf.get("Files", []): + Pattern = self.Parse(str(Entry.get("Pattern", "")), Parent=Parent) + if Entry.get("CaseSensitive", 0) == 1: + Matched = fnmatch.fnmatchcase(FileName, Pattern) + else: + Matched = fnmatch.fnmatchcase(FileName.lower(), Pattern.lower()) + if Matched: + Steps.append(f"The input lets it through ({Entry.get('Pattern')}).") + break + if not Matched: + Steps.append("None of the input's own patterns match, so it stays where it is.") + return SimulationResult(Steps, None, "Skipped") + + for OutputPath in self.Outputs: + OutConf = self.LoadDirConf(OutputPath, "Out") + if OutConf is None: + Steps.append(f"{OutputPath}: its config can't be read, skipped.") + continue + if not self._DirAccepts(OutConf, OutputPath, InputPath): + Steps.append(f"{OutputPath}: its ValidInputDirs don't include this input.") + continue + Limit = OutConf.get("FileLimit", 0) or 0 + if Limit > 0 and os.path.isdir(OutputPath): + Count = sum( + 1 for Name in os.listdir(OutputPath) + if not Name.startswith(".") and os.path.isfile(os.path.join(OutputPath, Name)) + ) + if Count >= Limit: + Steps.append(f"{OutputPath}: full ({Count}/{Limit} files).") + continue + for Entry in OutConf.get("Files", []): + if not self._EntryAccepts(Entry, OutputPath, InputPath): + continue + Pattern = str(Entry.get("Pattern", "")) + if Entry.get("CaseSensitive", 0) == 1: + Match = fnmatch.fnmatchcase(FileName, Pattern) + else: + Match = fnmatch.fnmatch(FileName.lower(), Pattern.lower()) + if not Match: + continue + NewName = self.PreviewName(str(Entry.get("NewFileName", "~^")), FileName, OutputPath, Entry, OutConf) + if Entry.get("Overwrite", 1) == 0 and os.path.exists(os.path.join(OutputPath, NewName)): + Steps.append(f"{OutputPath}: {Pattern} matches, but {NewName} exists and Overwrite = 0.") + continue + Action = "moved" if OutConf.get("DeleteOrg", 1) == 1 else "copied" + Steps.append(f"{OutputPath}: {Pattern} matches, so it's {Action} and named {NewName}.") + return SimulationResult(Steps, f"{OutputPath}/{NewName}", "Sorted") + Steps.append(f"{OutputPath}: no pattern matches.") + Failed = self.SpecialDir("FailedDir") + Steps.append(f"No output takes it, so it goes to {Failed}.") + return SimulationResult(Steps, f"{Failed}/{FileName}" if Failed else None, "Failed") + + def PreviewName( + self, NewFileName: str, FileName: str, OutputPath: str, Entry: Optional[dict] = None, + OutConf: Optional[dict] = None, + ) -> str: + Entry = Entry or {} + NextChar = str(Entry.get("NextChar", "A")) + # Init() pads NextChar to the longest run of the NextChar variable. + Character = self.Vars.get("NextChar", "&") + Runs = re.findall(f"{re.escape(Character)}+", NewFileName) if Character else [] + Width = max((len(Run) for Run in Runs), default=0) + if Width: + NextChar = NextChar.upper().rjust(Width, "A") + return self.Parse( + NewFileName, ConfData=OutConf if OutConf is not None else {}, OrgFile=FileName, + NextNum=Entry.get("NextNum", "1"), NextChar=NextChar, Parent=os.path.basename(OutputPath), + ) + + # Problems worth showing + + def Warnings(self) -> list[tuple[str, str]]: + """(Level, Text) pairs; Level is "Error", "Warning" or "Info".""" + Found: list[tuple[str, str]] = [] + + def Walk(Data: Any, Where: str) -> None: + if isinstance(Data, dict): + for Key, Value in Data.items(): + Walk(Value, f"{Where}{Key}" if not Where else f"{Where}.{Key}") + elif Data is None or Data == "Unset": + Found.append(("Error", f"GlobalConf.toml: {Where} is Unset. With SafeMode = 1 SaraSortd won't start.")) + + Walk(self.Data, "") + + Daemon = self.DaemonVersion() + ConfVersion = str(self.Data.get("Version", "?")) + if Daemon is None: + Found.append(("Warning", f"Can't find SaraSortd.py at {self.Config.SortdScriptPath} (see Settings).")) + elif MajorVersion(Daemon) != MajorVersion(ConfVersion): + Found.append(("Error", f"GlobalConf.toml is version {ConfVersion}, but SaraSortd is {Daemon}: the first number differs, so the config won't work.")) + + Call = self.Vars.get("VarCall", "%%") + if len(Call) >= 2: + Leftover = re.compile(f"{re.escape(Call[0])}[^{re.escape(Call[1])}]+{re.escape(Call[1])}") + for Key in ("InputDir", "OutputDir"): + for Raw in self.DirList(Key): + if Leftover.search(self.ResolveDir(Raw)): + Found.append(("Error", f"{Raw} doesn't resolve (SaraSortd only replaces one level of variables).")) + + Inputs = list(dict.fromkeys(self.Inputs)) + Outputs = list(dict.fromkeys(self.Outputs)) + Edges = self.Edges() + UsesOwnInputs = False + for DirPath in dict.fromkeys(Inputs + Outputs): + if not os.path.isdir(DirPath): + Found.append(("Info", f"{DirPath} doesn't exist yet; SaraSortd makes it when it starts.")) + for Kind in (["In"] if DirPath in Inputs else []) + (["Out"] if DirPath in Outputs else []): + if os.path.isdir(DirPath) and not self.ConfFile(DirPath, Kind).exists(): + Found.append(("Info", f"{DirPath} has no {'input' if Kind == 'In' else 'output'} config yet; SaraSortd copies the template when it starts.")) + + for InputPath in Inputs: + if not any(FoundEdge.Input == InputPath for FoundEdge in Edges): + Found.append(("Warning", f"{InputPath} has no outputs: every file it lets through goes to FailedDir.")) + for OutputPath in Outputs: + OutConf = self.LoadDirConf(OutputPath, "Out") + if OutConf is None: + continue + if not any(FoundEdge.Output == OutputPath for FoundEdge in Edges): + Found.append(("Warning", f"{OutputPath} can't receive anything: no input matches its ValidInputDirs and patterns.")) + if "*" in [str(Valid) for Valid in OutConf.get("ValidInputDirs", [])] and any( + "ValidInputDirs" not in Entry for Entry in OutConf.get("Files", []) + ): + Found.append(("Info", f"{OutputPath} takes files from every input (ValidInputDirs = [\"*\"]).")) + Entries = OutConf.get("Files", []) + for Position, Entry in enumerate(Entries): + UsesOwnInputs = UsesOwnInputs or "ValidInputDirs" in Entry + for Missing in ("Pattern", "NewFileName", "NextNum", "NextChar", "CaseSensitive", "Overwrite"): + if Missing not in Entry: + Found.append(("Error", f"{OutputPath}: [[Files]] #{Position + 1} has no {Missing}; SaraSortd will crash on it.")) + if Entry.get("Pattern") == "*" and "ValidInputDirs" not in Entry and Position < len(Entries) - 1: + Found.append(("Warning", f"{OutputPath}: [[Files]] #{Position + 1} is a catch-all \"*\", so the entries after it never get a file.")) + for InputPath in Inputs: + # SaraSortd tries outputs in OutputDir order and stops at the first match. + CatchAll = None + for FoundEdge in sorted( + (FoundEdge for FoundEdge in Edges if FoundEdge.Input == InputPath), + key=lambda FoundEdge: Outputs.index(FoundEdge.Output), + ): + if CatchAll: + Found.append(("Warning", f"{CatchAll} takes every file from {InputPath} first, so {FoundEdge.Output} never gets any ({', '.join(FoundEdge.Patterns)}).")) + elif "*" in FoundEdge.Patterns: + CatchAll = FoundEdge.Output + if UsesOwnInputs and Daemon and not VersionAtLeast(Daemon, (7, 1)): + Found.append(("Error", f"Some [[Files]] entries have their own ValidInputDirs, which SaraSortd {Daemon} ignores. Update SaraSortd to 7.1.")) + + for Raw in self.DirList("InputDir") + self.DirList("OutputDir"): + Odd = [Char for Char in (self.Vars.get(Key) for Key in ("NextNum", "NextChar", "Parent", "OrgFileName", "OrgFileType")) if Char and Char in Raw] + if Odd: + Found.append(("Warning", f"{Raw} contains {', '.join(Odd)}, which SaraSortd swaps for a variable.")) + + Order = {"Error": 0, "Warning": 1, "Info": 2} + return sorted(Found, key=lambda Item: Order[Item[0]]) diff --git a/Templates.py b/Templates.py new file mode 100644 index 0000000..acc9a74 --- /dev/null +++ b/Templates.py @@ -0,0 +1,130 @@ +"""Ready-made connections ("Pictures sorting" and so on): a list of patterns +plus a few settings, installed between an input and an output you pick. + +Built-ins live here. Your own are TOML files in +~/.config/SaraSuite/SaraSort/Templates/, same keys as SortTemplate: + + Name = "3D prints" + Description = "Models and slicer files." + Icon = "VIEW_IN_AR" + Patterns = ["*.stl", "*.3mf"] + NewFileName = "~^" # optional; OutputDirConf.toml's default otherwise + SuggestedOutput = "~/Prints" # optional + [OutputSettings] # optional, top-level keys of the output's config + DeleteOrg = 1 + [InputSettings] # optional, top-level keys of the input's config + SortDirs = 0 +""" + +import re +from dataclasses import asdict, dataclass, field +from pathlib import Path +from typing import Optional + +import tomlkit + +from Core import UserTemplatesDir + + +@dataclass +class SortTemplate: + Name: str + Description: str = "" + Icon: str = "FOLDER_COPY_OUTLINED" + Patterns: list[str] = field(default_factory=list) + NewFileName: str = "" # empty: OutputDirConf.toml's default + SuggestedOutput: str = "" + OutputSettings: dict = field(default_factory=dict) + InputSettings: dict = field(default_factory=dict) + FilePath: Optional[Path] = None # set for your own templates + + @property + def BuiltIn(self) -> bool: + return self.FilePath is None + + +BuiltInTemplates = [ + SortTemplate( + "Pictures", "Photos, screenshots and other images.", "IMAGE_OUTLINED", + ["*.jp*g", "*.png", "*.gif", "*.webp", "*.bmp", "*.svg", "*.heic", "*.tif*", "*.avif"], + SuggestedOutput="~/Pictures", + ), + SortTemplate( + "Videos", "Video files.", "MOVIE_OUTLINED", + ["*.mp4", "*.mkv", "*.webm", "*.mov", "*.avi", "*.m4v"], + SuggestedOutput="~/Videos", + ), + SortTemplate( + "Music", "Audio files.", "MUSIC_NOTE_OUTLINED", + ["*.mp3", "*.flac", "*.ogg", "*.opus", "*.wav", "*.m4a", "*.aac"], + SuggestedOutput="~/Music", + ), + SortTemplate( + "Documents", "PDFs, office files and text.", "DESCRIPTION_OUTLINED", + ["*.pdf", "*.doc*", "*.odt", "*.rtf", "*.txt", "*.md", "*.xls*", "*.ods", "*.csv", "*.ppt*", "*.odp", "*.epub"], + SuggestedOutput="~/Documents", + ), + SortTemplate( + "Archives (unpacked)", "Archives, unpacked in the output and the archive removed.", "FOLDER_ZIP_OUTLINED", + ["*.zip", "*.tar", "*.tar.gz", "*.tgz", "*.tar.xz", "*.tar.bz2"], + OutputSettings={"Unzip": ["zip", "tar", "tar.gz", "tgz", "tar.xz", "tar.bz2"]}, + ), + SortTemplate( + "Git repositories", "Folders (e.g. cloned repos) are zipped by the input, moved and unzipped in the output.", + "ACCOUNT_TREE_OUTLINED", ["*.zip"], + SuggestedOutput="~/Applications", + OutputSettings={"Unzip": ["zip"]}, + InputSettings={"SortDirs": 1}, + ), + SortTemplate( + "Logs", "Log files, renamed with today's date in front.", "RECEIPT_LONG_OUTLINED", + ["*.log", "*.log.*"], NewFileName="[y].[m].[d]_~^", + ), + SortTemplate( + "3D printing", "Models, OpenSCAD files and slicer output.", "VIEW_IN_AR_OUTLINED", + ["*.stl", "*.3mf", "*.scad", "*.step", "*.stp", "*.obj", "*.gcode", "*.bgcode"], + ), + SortTemplate( + "Installers", "AppImages, packages and Flatpak refs.", "INSTALL_DESKTOP_OUTLINED", + ["*.AppImage", "*.rpm", "*.deb", "*.flatpak", "*.flatpakref"], + ), + SortTemplate( + "Backup copy", "Copies everything instead of moving it (DeleteOrg = 0).", "CONTENT_COPY_OUTLINED", + ["*"], OutputSettings={"DeleteOrg": 0}, + ), +] + + +def UserTemplates() -> list[SortTemplate]: + if not UserTemplatesDir.is_dir(): + return [] + Found = [] + Known = set(SortTemplate.__dataclass_fields__) - {"FilePath"} + for FilePath in sorted(UserTemplatesDir.glob("*.toml")): + try: + Data = tomlkit.parse(FilePath.read_text()).unwrap() + except (OSError, tomlkit.exceptions.TOMLKitError): + continue + Values = {Key: Value for Key, Value in Data.items() if Key in Known} + Values.setdefault("Name", FilePath.stem) + Found.append(SortTemplate(**Values, FilePath=FilePath)) + return Found + + +def AllTemplates() -> list[SortTemplate]: + return BuiltInTemplates + UserTemplates() + + +def SaveUserTemplate(Template: SortTemplate) -> Path: + UserTemplatesDir.mkdir(parents=True, exist_ok=True) + FileName = re.sub(r"[^A-Za-z0-9]+", "", Template.Name.title()) or "Template" + FilePath = UserTemplatesDir / f"{FileName}.toml" + Data = {Key: Value for Key, Value in asdict(Template).items() if Key != "FilePath" and Value not in ("", {}, [])} + Data["Patterns"] = Template.Patterns + FilePath.write_text(tomlkit.dumps(Data)) + return FilePath + + +def DeleteUserTemplate(Template: SortTemplate) -> None: + if Template.FilePath: + Template.FilePath.unlink(missing_ok=True) diff --git a/TemplatesPage.py b/TemplatesPage.py new file mode 100644 index 0000000..75a0533 --- /dev/null +++ b/TemplatesPage.py @@ -0,0 +1,168 @@ +"""Ready-made connections to install between an input and an output you pick, +plus your own templates (saved from an arrow, or made here).""" + +import os + +import flet as ft + +from Core import ChipButton, DisplayPath, Heading, Note, Palette +from Templates import AllTemplates, DeleteUserTemplate, SaveUserTemplate, SortTemplate +from Widgets import App, DirField, ExpandPath, NameField + +CardWidth = 300 + + +class TemplatesPage(ft.Column): + def __init__(self, TheApp: App): + super().__init__(expand=True, spacing=0) + self.App = TheApp + self.Grid = ft.Row(wrap=True, spacing=12, run_spacing=12, vertical_alignment=ft.CrossAxisAlignment.START) + self.controls = [ + ft.Container( + ft.Row([ + Heading("Templates"), ft.Container(expand=True), + ChipButton("New template", lambda e: self._EditDialog(None), ft.Icons.ADD), + ]), + padding=ft.Padding.only(left=16, right=16, top=12, bottom=4), + ), + ft.Container(Note("Pick one, choose where files come from and where they go. " + "Your own templates live in ~/.config/SaraSuite/SaraSort/Templates/."), + padding=ft.Padding.only(left=16, bottom=8)), + ft.Divider(height=1, color=Palette.Surface1), + ft.Container(ft.Column([self.Grid], scroll=ft.ScrollMode.AUTO, expand=True), padding=16, expand=True), + ] + + def Refresh(self) -> None: + self.Grid.controls = [self._Card(Template) for Template in AllTemplates()] + self.update() + + def _Card(self, Template: SortTemplate) -> ft.Container: + Icon = getattr(ft.Icons, Template.Icon, ft.Icons.FOLDER_COPY_OUTLINED) + Patterns = ", ".join(Template.Patterns) + Extras = [] + if Template.OutputSettings.get("Unzip"): + Extras.append("unpacks archives") + if Template.OutputSettings.get("DeleteOrg") == 0: + Extras.append("copies") + if Template.InputSettings.get("SortDirs"): + Extras.append("sorts folders") + if Template.NewFileName: + Extras.append(f"renames to {Template.NewFileName}") + Buttons = [ChipButton("Install", lambda e: self._InstallDialog(Template), ft.Icons.DOWNLOAD_OUTLINED)] + if not Template.BuiltIn: + Buttons += [ + ft.IconButton(ft.Icons.EDIT_OUTLINED, tooltip="Edit", on_click=lambda e: self._EditDialog(Template)), + ft.IconButton(ft.Icons.DELETE_OUTLINE, icon_color=Palette.Red, tooltip="Delete template", + on_click=lambda e: self.App.Confirm("Delete template", f"Delete \"{Template.Name}\"?", + lambda: (DeleteUserTemplate(Template), self.Refresh()), "Delete")), + ] + return ft.Container( + ft.Column([ + ft.Row([ft.Icon(Icon, color=Palette.Mauve), ft.Text(Template.Name, size=16, expand=True), + Note("built-in" if Template.BuiltIn else "yours", 11)]), + ft.Text(Template.Description or " ", size=12), + ft.Text(Patterns, size=12, color=Palette.Mauve, max_lines=2, overflow=ft.TextOverflow.ELLIPSIS, tooltip=Patterns), + Note(", ".join(Extras), 11) if Extras else ft.Container(height=0), + ft.Row(Buttons), + ], spacing=8), + width=CardWidth, padding=14, bgcolor=Palette.Mantle, border=ft.Border.all(1, Palette.Surface1), border_radius=6, + ) + + def _InstallDialog(self, Template: SortTemplate) -> None: + TheSetup, Error = self.App.LoadSetup() + if TheSetup is None: + self.App.Notify(Error, "error") + return + Root = TheSetup.RootDir() or os.path.expanduser("~") + DefaultInput = next(iter(TheSetup.Inputs), f"{Root}/Input") + DefaultOutput = ExpandPath(Template.SuggestedOutput) if Template.SuggestedOutput else f"{Root}/{Template.Name.split(' ')[0]}" + InputRow, InputField = DirField(self.App, "Input (files come from)", DefaultInput) + OutputRow, OutputField = DirField(self.App, "Output (files go to)", DefaultOutput) + Inputs = list(dict.fromkeys(TheSetup.Inputs)) + Quick = ft.Row([ + ft.Container(ft.Text(DisplayPath(PathText), size=11, color=Palette.Red), padding=ft.Padding.symmetric(horizontal=6, vertical=2), + border=ft.Border.all(1, Palette.Surface1), border_radius=4, + on_click=lambda e, PathText=PathText: (setattr(InputField, "value", PathText), InputField.update())) + for PathText in Inputs + ], wrap=True, spacing=4) + PatternField = ft.TextField(label="Patterns", value=", ".join(Template.Patterns), dense=True, multiline=True, max_lines=3) + Defaults = TheSetup.TemplateFileDefaults() + Name = NameField(TheSetup, Template.NewFileName or str(Defaults.get("NewFileName", "~^")), DefaultOutput, Defaults) + Move = ft.Switch(label="Move files (off: copy them)", value=Template.OutputSettings.get("DeleteOrg", 1) == 1) + + def Install(e) -> None: + InputPath, OutputPath = ExpandPath(InputField.value or ""), ExpandPath(OutputField.value or "") + Patterns = [Part.strip() for Part in (PatternField.value or "").split(",") if Part.strip()] + if not InputPath or not OutputPath or not Patterns: + self.App.Notify("Pick an input, an output and at least one pattern.", "warning") + return + if InputPath == OutputPath: + self.App.Notify("Input and output are the same directory.", "warning") + return + self.App.Close() + OutputSettings = dict(Template.OutputSettings) + OutputSettings["DeleteOrg"] = 1 if Move.value else 0 + Overrides = {} + if Name.value and Name.value != Defaults.get("NewFileName"): + Overrides["NewFileName"] = Name.value + try: + Changed = TheSetup.Connect(InputPath, OutputPath, Patterns, Overrides, OutputSettings, dict(Template.InputSettings)) + except Exception as Error: + self.App.Notify(f"Couldn't install: {Error}", "error") + return + if Changed: + self.App.AfterGlobalChange() + else: + self.App.Notify(f"Installed {Template.Name}.", "success") + self.App.Navigate("Flow") + + Notes = [] + if Template.InputSettings.get("SortDirs"): + Notes.append(Note("Turns on SortDirs for the input: folders dropped there are zipped and sorted too.")) + if Template.OutputSettings.get("Unzip"): + Notes.append(Note(f"The output unpacks {', '.join(Template.OutputSettings['Unzip'])} files and removes the archive.")) + self.App.ShowDialog(f"Install: {Template.Name}", ft.Column([ + InputRow, Quick, OutputRow, PatternField, Name, Move, *Notes, + ], tight=True, spacing=10, scroll=ft.ScrollMode.AUTO), [ + ChipButton("Cancel", lambda e: self.App.Close()), ChipButton("Install", Install), + ], Width=640) + + def _EditDialog(self, Template: SortTemplate | None) -> None: + TheSetup, _ = self.App.LoadSetup() + NameInput = ft.TextField(label="Name", value=Template.Name if Template else "", dense=True, autofocus=True) + Description = ft.TextField(label="Description", value=Template.Description if Template else "", dense=True) + PatternField = ft.TextField(label="Patterns, separated by commas", value=", ".join(Template.Patterns) if Template else "", + dense=True, multiline=True, max_lines=3) + Suggested = ft.TextField(label="Suggested output (optional)", value=Template.SuggestedOutput if Template else "", dense=True) + NewName = NameField(TheSetup, Template.NewFileName if Template else "", "/Output", Label="NewFileName (empty: the default)") + Move = ft.Switch(label="Move files (off: copy them)", value=(Template.OutputSettings.get("DeleteOrg", 1) if Template else 1) == 1) + Unzip = ft.TextField(label="Unzip these types (optional, commas)", dense=True, + value=", ".join(Template.OutputSettings.get("Unzip", [])) if Template else "") + SortDirs = ft.Switch(label="Input also sorts folders (SortDirs)", value=bool(Template and Template.InputSettings.get("SortDirs"))) + + def Save(e) -> None: + if not (NameInput.value or "").strip(): + return + self.App.Close() + OutputSettings = dict(Template.OutputSettings) if Template else {} + OutputSettings["DeleteOrg"] = 1 if Move.value else 0 + UnzipTypes = [Part.strip() for Part in (Unzip.value or "").split(",") if Part.strip()] + if UnzipTypes: + OutputSettings["Unzip"] = UnzipTypes + else: + OutputSettings.pop("Unzip", None) + New = SortTemplate( + NameInput.value.strip(), Description.value or "", Template.Icon if Template else "BOOKMARK_OUTLINED", + [Part.strip() for Part in (PatternField.value or "").split(",") if Part.strip()], + NewName.value, Suggested.value or "", OutputSettings, {"SortDirs": 1} if SortDirs.value else {}, + ) + if Template and Template.FilePath: + DeleteUserTemplate(Template) + SaveUserTemplate(New) + self.Refresh() + + self.App.ShowDialog("Edit template" if Template else "New template", ft.Column([ + NameInput, Description, PatternField, Suggested, NewName, Move, Unzip, SortDirs, + ], tight=True, spacing=10, scroll=ft.ScrollMode.AUTO), [ + ChipButton("Cancel", lambda e: self.App.Close()), ChipButton("Save", Save), + ], Width=600) diff --git a/ToolsPage.py b/ToolsPage.py new file mode 100644 index 0000000..1e6663c --- /dev/null +++ b/ToolsPage.py @@ -0,0 +1,191 @@ +"""Tools: warnings about the current setup, a filename tester that shows where +a file would go, and the files that failed to sort (with a retry).""" + +import datetime +import os +import shutil +from pathlib import Path + +import flet as ft + +from Core import ChipButton, ChipButtonStyle, DisplayPath, Heading, Note, Palette +from Sortd import Setup +from Widgets import App, EmptyState + +LevelColors = {"Error": Palette.Red, "Warning": Palette.Yellow, "Info": Palette.Blue} +LevelIcons = {"Error": ft.Icons.ERROR_OUTLINE, "Warning": ft.Icons.WARNING_AMBER, "Info": ft.Icons.INFO_OUTLINE} + + +def HumanSize(Size: int) -> str: + for Unit in ("B", "KB", "MB", "GB"): + if Size < 1024 or Unit == "GB": + return f"{Size:.0f} {Unit}" if Unit == "B" else f"{Size:.1f} {Unit}" + Size /= 1024 + return str(Size) + + +def FreeName(Folder: Path, Name: str) -> Path: + """Folder/Name, or Folder/"Name (2)" etc. if that's taken.""" + Target = Folder / Name + Stem, Suffix = os.path.splitext(Name) + Count = 2 + while Target.exists(): + Target = Folder / f"{Stem} ({Count}){Suffix}" + Count += 1 + return Target + + +class ToolsPage(ft.Column): + def __init__(self, TheApp: App): + super().__init__(expand=True, spacing=0) + self.App = TheApp + self.Section = "Warnings" + self.TheSetup: Setup | None = None + self.Buttons = {Name: ChipButton(Name, lambda e, Name=Name: self._Show(Name)) + for Name in ("Warnings", "Tester", "Failed files")} + self.Body = ft.Container(expand=True, padding=16) + + # Tester + self.TestInput = ft.Dropdown(label="Input directory", dense=True, width=360) + self.TestName = ft.TextField(label="File name", value="Holiday photo.png", dense=True, expand=True, + on_submit=lambda e: self._RunTest()) + self.TestResult = ft.Column(spacing=6) + + # Failed files + self.RetryInput = ft.Dropdown(label="Retry into", dense=True, width=360) + self.FailedList = ft.ListView(spacing=2, expand=True) + + self.controls = [ + ft.Container( + ft.Row([Heading("Tools"), ft.Container(width=12), *self.Buttons.values(), ft.Container(expand=True), + ChipButton("Reload", lambda e: self.Refresh(), ft.Icons.REFRESH)]), + padding=ft.Padding.only(left=16, right=16, top=12, bottom=8), + ), + ft.Divider(height=1, color=Palette.Surface1), + self.Body, + ] + + def Refresh(self) -> None: + self.TheSetup, Error = self.App.LoadSetup() + if self.TheSetup is None: + self.Body.content = EmptyState("Can't read SaraSortd's config.", Error) + self.update() + return + Inputs = list(dict.fromkeys(self.TheSetup.Inputs)) + for Picker in (self.TestInput, self.RetryInput): + Current = Picker.value + Picker.options = [ft.DropdownOption(key=PathText, text=DisplayPath(PathText)) for PathText in Inputs] + Picker.value = Current if Current in Inputs else (Inputs[0] if Inputs else None) + self._Show(self.Section) + + def _Show(self, Name: str) -> None: + self.Section = Name + for ButtonName, Button in self.Buttons.items(): + Button.style = ChipButtonStyle(Palette.Mauve if ButtonName == Name else Palette.Surface1) + if self.TheSetup is not None: + self.Body.content = {"Warnings": self._Warnings, "Tester": self._Tester, "Failed files": self._Failed}[Name]() + self.update() + + # Warnings + + def _Warnings(self) -> ft.Control: + Found = self.TheSetup.Warnings() + if not Found: + return EmptyState("Nothing to warn about.", "SaraSortd's setup looks fine.") + Counts = {Level: sum(1 for Item in Found if Item[0] == Level) for Level in LevelColors} + return ft.Column([ + Note(f"{Counts['Error']} errors, {Counts['Warning']} warnings, {Counts['Info']} notes"), + ft.ListView([ + ft.Container( + ft.Row([ + ft.Icon(LevelIcons[Level], color=LevelColors[Level], size=18), + ft.Text(Text, size=13, selectable=True, expand=True), + ], vertical_alignment=ft.CrossAxisAlignment.START), + padding=10, bgcolor=Palette.Mantle, border=ft.Border.only(left=ft.BorderSide(3, LevelColors[Level])), + border_radius=4, + ) + for Level, Text in Found + ], spacing=6, expand=True), + ], expand=True) + + # Tester + + def _Tester(self) -> ft.Control: + return ft.Column([ + Note("Where would a file go? Follows SaraSortd's own rules without moving anything."), + ft.Row([self.TestInput, self.TestName, ChipButton("Test", lambda e: self._RunTest(), ft.Icons.PLAY_ARROW)]), + ft.Divider(height=1, color=Palette.Surface1), + ft.Column([self.TestResult], scroll=ft.ScrollMode.AUTO, expand=True), + ], expand=True, spacing=10) + + def _RunTest(self) -> None: + if not self.TestInput.value or not (self.TestName.value or "").strip(): + return + Result = self.TheSetup.Simulate(self.TestInput.value, self.TestName.value.strip()) + Color = {"Sorted": Palette.Green, "Failed": Palette.Red, "Skipped": Palette.Yellow}[Result.Outcome] + self.TestResult.controls = [ + ft.Text({"Sorted": "Sorted to", "Failed": "Fails, goes to", "Skipped": "Not sorted"}[Result.Outcome], color=Color, size=16), + ft.Text(DisplayPath(Result.Destination) if Result.Destination else "-", selectable=True, size=15), + ft.Container(height=6), + *[ft.Text(f"{Index + 1}. {Step}", size=12, color=Palette.Overlay0, selectable=True) for Index, Step in enumerate(Result.Steps)], + ] + self.TestResult.update() + + # Failed files + + def _FailedDir(self) -> Path | None: + return self.TheSetup.SpecialDir("FailedDir") + + def _Failed(self) -> ft.Control: + FailedDir = self._FailedDir() + Files = sorted((Item for Item in FailedDir.iterdir() if not Item.name.startswith(".")), + key=lambda Item: Item.stat().st_mtime, reverse=True) if FailedDir and FailedDir.is_dir() else [] + Rows = [] + for Item in Files: + Stat = Item.stat() + Rows.append(ft.Container(ft.Row([ + ft.Icon(ft.Icons.FOLDER_OUTLINED if Item.is_dir() else ft.Icons.INSERT_DRIVE_FILE_OUTLINED, size=18, color=Palette.Overlay0), + ft.Text(Item.name, size=13, expand=True, selectable=True, no_wrap=True, overflow=ft.TextOverflow.ELLIPSIS), + Note(HumanSize(Stat.st_size) if Item.is_file() else "folder", 11), + Note(datetime.datetime.fromtimestamp(Stat.st_mtime).strftime("%Y-%m-%d %H:%M"), 11), + ft.IconButton(ft.Icons.TROUBLESHOOT, tooltip="Test where it would go now", + on_click=lambda e, Name=Item.name: self._TestFailed(Name)), + ft.IconButton(ft.Icons.REPLAY, tooltip="Move back into the input to retry", + on_click=lambda e, Item=Item: self._Retry([Item])), + ]), padding=ft.Padding.symmetric(horizontal=8), bgcolor=Palette.Mantle, border_radius=4)) + self.FailedList.controls = Rows or [Note("No failed files.")] + return ft.Column([ + ft.Row([ + ft.Text(DisplayPath(str(FailedDir)) if FailedDir else "No FailedDir set", color=Palette.Mauve, expand=True), + self.RetryInput, + ChipButton("Retry all", lambda e: self._Retry(Files), ft.Icons.REPLAY, Disabled=not Files), + ChipButton("Open folder", lambda e: self.App.OpenFolder(str(FailedDir)), ft.Icons.FOLDER_OPEN_OUTLINED, + Disabled=not (FailedDir and FailedDir.is_dir())), + ]), + Note("Retrying moves files back into the input; if no output takes them, SaraSortd puts them back here. " + "Fix the arrows first (the test button shows what would happen)."), + self.FailedList, + ], expand=True, spacing=10) + + def _TestFailed(self, Name: str) -> None: + self.TestName.value = Name + if self.RetryInput.value: + self.TestInput.value = self.RetryInput.value + self._Show("Tester") + self._RunTest() + + def _Retry(self, Items: list[Path]) -> None: + Target = self.RetryInput.value + if not Target: + self.App.Notify("Pick an input to retry into.", "warning") + return + Moved = 0 + for Item in Items: + try: + shutil.move(str(Item), str(FreeName(Path(Target), Item.name))) + Moved += 1 + except OSError as Error: + self.App.Notify(f"Couldn't move {Item.name}: {Error}", "error") + if Moved: + self.App.Notify(f"Moved {Moved} file{'s' if Moved != 1 else ''} back into {DisplayPath(Target)}.", "success") + self._Show("Failed files") diff --git a/Widgets.py b/Widgets.py new file mode 100644 index 0000000..1312205 --- /dev/null +++ b/Widgets.py @@ -0,0 +1,162 @@ +"""What every page shares: the App context (config, notifications, reloading +SaraSortd's setup, restarting it after changes) and small reusable controls.""" + +import os +import subprocess +from typing import Callable, Optional + +import flet as ft + +from Core import AppConfig, ChildEnvironment, ChipButton, Note, Palette +from Service import RestartIfRunning +from Sortd import Setup, SortdError + + +class App: + def __init__(self, Page: ft.Page, Config: AppConfig): + self.Page = Page + self.Config = Config + self.Picker = ft.FilePicker() # registers itself with the page + self.Navigate: Callable[..., None] = lambda Name, **Arguments: None # set by Main + + def Notify(self, Text: str, Kind: str = "info") -> None: + Color = {"success": Palette.Green, "error": Palette.Red, "warning": Palette.Yellow}.get(Kind, Palette.Text) + self.Page.show_dialog(ft.SnackBar( + ft.Text(Text, color=Color), bgcolor=Palette.Surface0, duration=ft.Duration(seconds=4), + )) + + def LoadSetup(self) -> tuple[Optional[Setup], str]: + try: + return Setup(self.Config), "" + except SortdError as Error: + return None, str(Error) + + def AfterGlobalChange(self) -> None: + """GlobalConf.toml changed; SaraSortd only reads it when it starts.""" + if not self.Config.AutoRestart: + self.Notify("Saved. Restart SaraSortd (Service page) for it to take effect.", "warning") + return + Error = RestartIfRunning(self.Config.ServiceName) + if Error: + self.Notify(f"Saved, but restarting SaraSortd failed: {Error}", "error") + else: + self.Notify("Saved; SaraSortd restarted.", "success") + + def ShowDialog(self, Title: str, Content: ft.Control, Actions: list[ft.Control], Width: int = 560) -> ft.AlertDialog: + Dialog = ft.AlertDialog( + title=Title, + content=ft.Container(Content, width=Width), + actions=Actions, + ) + self.Page.show_dialog(Dialog) + return Dialog + + def Close(self) -> None: + self.Page.pop_dialog() + + def Confirm(self, Title: str, Text: str, OnYes: Callable[[], None], YesText: str = "Remove") -> None: + def Yes(e) -> None: + self.Close() + OnYes() + + self.ShowDialog(Title, ft.Text(Text), [ + ChipButton("Cancel", lambda e: self.Close()), + ChipButton(YesText, Yes, Accent=Palette.Red), + ], Width=440) + + def OpenFolder(self, PathText: str) -> None: + try: + subprocess.Popen(["xdg-open", PathText], env=ChildEnvironment(), start_new_session=True) + except OSError as Error: + self.Notify(f"Couldn't open {PathText}: {Error}", "error") + + +def DirField(TheApp: App, Label: str, Value: str = "", Expand: bool = True) -> tuple[ft.Row, ft.TextField]: + """A path field with a Browse button. Returns (row, field).""" + Field = ft.TextField(label=Label, value=Value, expand=Expand, dense=True) + + async def Browse(e) -> None: + Chosen = await TheApp.Picker.get_directory_path(dialog_title=Label, initial_directory=Field.value or None) + if Chosen: + Field.value = Chosen + Field.update() + + return ft.Row([Field, ft.IconButton(ft.Icons.FOLDER_OPEN_OUTLINED, tooltip="Browse", on_click=Browse)]), Field + + +def ExpandPath(Text: str) -> str: + return os.path.abspath(os.path.expanduser(Text.strip())).rstrip("/") if Text.strip() else "" + + +class NameField(ft.Column): + """A NewFileName field with buttons that insert SaraSortd's variables and + a live example of the resulting name.""" + + def __init__(self, TheSetup: Optional[Setup], Value: str, OutputPath: str = "", Entry: Optional[dict] = None, + Label: str = "NewFileName", OnChange: Optional[Callable[[str], None]] = None): + super().__init__(spacing=4) + self.TheSetup = TheSetup + self.OutputPath = OutputPath or "/Output" + self.Entry = Entry or {} + self.OnChangeCallback = OnChange + self.ExampleFile = ft.TextField(value="Holiday photo.png", label="Example file", dense=True, width=180, + on_change=lambda e: self._Refresh()) + self.Field = ft.TextField(label=Label, value=Value, dense=True, expand=True, on_change=lambda e: self._Changed()) + self.Preview = ft.Text("", color=Palette.Green, size=12, selectable=True) + Chips = [] + if TheSetup: + V = TheSetup.Vars + for Key, Tip in [ + ("OrgFileName", "original name"), ("OrgFileType", "original extension"), ("NextNum", "next number"), + ("NextChar", "next letter"), ("Parent", "folder name"), ("Year", "year"), ("Month", "month"), + ("Day", "day"), ("Hour", "hour"), ("Minute", "minute"), ("Second", "second"), + ]: + if V.get(Key): + Chips.append(ft.Container( + ft.Text(f"{V[Key]}", size=12, color=Palette.Mauve), + tooltip=f"{Key}: {Tip}", padding=ft.Padding.symmetric(horizontal=6, vertical=2), + border=ft.Border.all(1, Palette.Surface1), border_radius=4, + on_click=lambda e, Text=V[Key]: self._Insert(Text), + )) + self.controls = [ + ft.Row([self.Field, self.ExampleFile]), + ft.Row(Chips, wrap=True, spacing=4, run_spacing=4), + self.Preview, + ] + self._Refresh(Update=False) + + @property + def value(self) -> str: + return self.Field.value or "" + + def _Insert(self, Text: str) -> None: + self.Field.value = (self.Field.value or "") + Text + self.Field.update() + self._Changed() + + def _Changed(self) -> None: + self._Refresh() + if self.OnChangeCallback: + self.OnChangeCallback(self.value) + + def _Refresh(self, Update: bool = True) -> None: + if self.TheSetup: + Example = self.ExampleFile.value or "file.txt" + try: + Name = self.TheSetup.PreviewName(self.value or "~^", Example, self.OutputPath, self.Entry) + self.Preview.value = f"{Example} -> {Name}" + except Exception as Error: # a half-typed pattern shouldn't break the dialog + self.Preview.value = f"(can't preview: {Error})" + if Update: + self.Preview.update() + + +def KindColor(Kind: str) -> str: + return {"Input": Palette.Red, "Output": Palette.Blue, "Both": Palette.Mauve}[Kind] + + +def EmptyState(Text: str, Detail: str = "") -> ft.Control: + return ft.Container( + ft.Column([ft.Text(Text, color=Palette.Overlay0, size=16), Note(Detail)], horizontal_alignment=ft.CrossAxisAlignment.CENTER), + alignment=ft.Alignment.CENTER, expand=True, + ) diff --git a/assets/Icon.png b/assets/Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4c8446cc4c567416647c1eab69ae9260f486f777 GIT binary patch literal 1257 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|GzJEi4W2HJAr*7p-ZjjU4Hs!h zv}SP%6UcQ~VzA=0(Ulu>t(C7bo!;v3Aj)XRG|%VfvaX~vzhu#CGFz}lVM&ph!1)HZ z!?Jt-|GqWx+1+PXSnnE zutjk-)Be5xZptxy_|L*{h(c5+Q`@k+{Jpz;;)=iBn=iU6oMB-|WWqp)zcaX1|9ts) z)%*Q+R*_%-ZfN1eX0_RV_6^@`ZKup+Q1HP(IiIT;cE5Y}sqEr=`FV0Sw<2E{GNK0v zD12-i4o<&%d_SLp= zdw6_!?f+}F7IlVSKKo(>E-<*wYWNfWKg#(ccxl@{^$pwZpO0Vr>!11a_^{glL23*f z!Wc+;ANzrAfA@aB{N(f2?=08$Enjbp-75@gvitv)e)@Sw?!cYT_Hrd!a@|;cgew;R z*fOZa*8a=>#s2R9+!~wj^^Eu8YHLqytWd$~uFdQRmc)zQ`@A_9t2EHr1>fKOeWA>= zwZ72o%V%IhRiEF#?|FUkztk7c-&+YY0HYNh@!n^ac>8pD|LT4Jf4xY#`u+UPU$+0- zAB#KN?>~n%t<1j99PxYJzm>Ot+1lSu!xceqcI~YGWBKl9BrpYINpRm7*oynldtmhj zW_&`lA|J+wU&pv(o z1e~fLL>$=c{-f^h{rkRk$FV22-RuVGFXu6QsP%sT^Z5*nY~lkcP(Wo0`yck>{WS~X SKj!m*1U+5-T-G@yGywo?r>dI( literal 0 HcmV?d00001 diff --git a/assets/fonts/Fixedsys_Excelsior_Regular.ttf b/assets/fonts/Fixedsys_Excelsior_Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a3dbddd04ebcc0fc6b13d8b4c2090eacc1e8dbd1 GIT binary patch literal 580724 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.0,<2.0 +flet-desktop>=1.0,<2.0 # the desktop client; otherwise Flet fetches it on first launch and quits once +tomlkit