Files
sarahandClaude Opus 5.5 f7ed23edf9 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 <[email protected]>
2026-10-07 22:23:36 +02:00

131 lines
4.8 KiB
Python

"""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)