"""Session history in .SaraTagHistory.toml: where each Record or Library was left (directory, filter, sort, selected file), recent files and the last updater runs. SaraTagConf.toml's [History] section says what's remembered and how much. Same schema as the terminal version wrote, so nothing is lost.""" from datetime import datetime from pathlib import Path from typing import Any import tomlkit from Library import ConfValue, DataError, HistoryPath, ReadToml, TagCore, WriteAtomically def _Settings() -> dict[str, Any]: Defaults = { "Enabled": True, "MaxFileHistory": 30, "MaxDirectoryHistory": 20, "MaxUpdateHistory": 20, "RememberFilter": True, "RememberSorting": True, "RememberLastDirectory": True, "RememberLastSelectedFile": True, "RememberRecentFiles": True, "RememberUpdaterMode": True, "RememberUpdates": True, } return {Key: ConfValue("History", Key, Value) for Key, Value in Defaults.items()} def _Load() -> dict[str, Any]: TagCore.EnsureConfigFiles() try: return ReadToml(HistoryPath) except DataError: return {"ConfigVersion": 1} # a broken history file never blocks anything def _Save(History: dict[str, Any]) -> None: Document = tomlkit.document() Document.add(tomlkit.comment("SaraTag session history.")) Document.add(tomlkit.comment("This file is managed automatically.")) Document.add(tomlkit.nl()) for Key, Value in History.items(): Document[Key] = Value try: WriteAtomically(HistoryPath, tomlkit.dumps(Document)) except OSError: pass # history is a convenience; failing to save it isn't worth an error def _Now() -> str: return datetime.now().astimezone().isoformat(timespec="seconds") def _Key(Root: Path | None, Library: str | None) -> tuple[str, str]: return ("Library", Library) if Library is not None else ("Root", str(Root.resolve()) if Root else "") def ExplorerState(Root: Path | None = None, Library: str | None = None) -> dict[str, Any]: """Where a Record (by Root) or Library (by name) was left, filtered by what [History] says to remember.""" Settings = _Settings() if not Settings["Enabled"]: return {} KeyName, KeyValue = _Key(Root, Library) State = next((Item for Item in _Load().get("ExplorerState", []) if Item.get(KeyName) == KeyValue), {}) State = dict(State) if "Sorts" not in State and State.get("SortType"): State["Sorts"] = [f"Tag:{State['SortTag']}" if State["SortType"] == "Tag" and State.get("SortTag") else State["SortType"]] for Setting, Keys in (("RememberFilter", ["Filter"]), ("RememberSorting", ["Sorts", "SortDirection"]), ("RememberLastDirectory", ["Directory"]), ("RememberLastSelectedFile", ["SelectedFile"])): if not Settings[Setting]: for Key in Keys: State.pop(Key, None) return State def SaveExplorerState(Root: Path | None, Library: str | None, Directory: Path | None, Filter: str, Sorts: list[str], Descending: bool, SelectedFile: Path | None) -> None: Settings = _Settings() if not Settings["Enabled"]: return History = _Load() KeyName, KeyValue = _Key(Root, Library) States = [Item for Item in History.get("ExplorerState", []) if Item.get(KeyName) != KeyValue] First = Sorts[0] if Sorts else "Name" State: dict[str, Any] = {KeyName: KeyValue} if Directory is not None: State["Directory"] = str(Directory) State.update({ "Filter": Filter, "SortType": "Tag" if First.startswith("Tag:") else First, "SortDirection": "Descending" if Descending else "Ascending", "Sorts": list(Sorts), "Updated": _Now(), }) if First.startswith("Tag:"): State["SortTag"] = First[4:] if SelectedFile is not None: State["SelectedFile"] = str(SelectedFile) History["ExplorerState"] = [State] + States[: max(0, int(Settings["MaxDirectoryHistory"]) - 1)] _Save(History) def RecordRecentFile(FilePath: Path) -> None: Settings = _Settings() if not (Settings["Enabled"] and Settings["RememberRecentFiles"]): return History = _Load() Others = [Item for Item in History.get("RecentFile", []) if Item.get("Path") != str(FilePath)] History["RecentFile"] = ([{"Path": str(FilePath), "Time": _Now()}] + Others)[: int(Settings["MaxFileHistory"])] _Save(History) def RecentFiles() -> list[Path]: return [Path(Item["Path"]) for Item in _Load().get("RecentFile", []) if Item.get("Path")] def UpdaterState() -> dict[str, Any]: Settings = _Settings() if not (Settings["Enabled"] and Settings["RememberUpdaterMode"]): return {} return dict(_Load().get("Updater", {})) def RecordUpdaterRun(Mode: str, DryRun: bool, Scope: str, ChangedBooks: list[Path]) -> None: Settings = _Settings() if not Settings["Enabled"]: return History = _Load() Now = _Now() if Settings["RememberUpdaterMode"]: History["Updater"] = {"LastMode": Mode, "LastRun": Now, "LastDryRun": DryRun, "LastScope": Scope} if Settings["RememberUpdates"] and ChangedBooks and not DryRun: New = [{"Path": str(BookPath), "Time": Now, "Mode": Mode, "DryRun": DryRun, "Scope": Scope} for BookPath in ChangedBooks] History["RecentUpdate"] = (New + list(History.get("RecentUpdate", [])))[: int(Settings["MaxUpdateHistory"])] _Save(History)