Files
SaraView/Backend/Settings.py
T

195 lines
5.5 KiB
Python

from __future__ import annotations
import re
import shutil
import tomllib
from pathlib import Path
from typing import Any
import tomli_w
from Core import ApplicationDirectory, Config
PaletteRoles = [
"Background",
"Surface",
"SurfaceAlt",
"Border",
"Text",
"TextMuted",
"Black",
"White",
"Red",
"Orange",
"Yellow",
"Green",
"Blue",
"Purple",
]
DefaultSettings: dict[str, Any] = {
"FontFamily": "Fixedsys Excelsior",
"FontSize": 14,
"ThemeName": "CatppuccinMocha",
"ShowHiddenFolders": False,
"ShowSvFiles": False,
"ShowBreadcrumbs": True,
"UseHyphenBullets": True,
"UseAsciiFolderIcons": True,
"NavigateIntoDirectories": False,
"ShowGitBlame": False,
"NewEntryButtonsAtTop": False,
"StickyToolbar": True,
"ConfirmBeforeDelete": True,
"VisibleInBlame": True,
"ShowBlameAvatars": False,
# "Live": Markdown is edited where it's shown rendered (Obsidian-style
# live preview). "Separate": rendered View and raw Edit are two modes.
"MarkdownEditing": "Live",
# Graph view: directories as dots too (off: only files and the references
# between them), and the mouse button that pans ("Left" or "Right"; the
# other one opens a dot).
"GraphShowDirectories": True,
"GraphPanButton": "Right",
# Directory dots: heading colours by depth (off: all Purple), and smaller
# the deeper they are.
"GraphColourByDepth": True,
"GraphShrinkByDepth": False,
}
DefaultThemesDirectory = ApplicationDirectory / "DefaultThemes"
def SanitizeThemeName(ThemeName: str) -> str:
if not ThemeName or "/" in ThemeName or "\\" in ThemeName or ThemeName in (".", ".."):
raise ValueError(f"Invalid theme name: {ThemeName}")
return ThemeName
def SanitizeUsernameForPath(Username: str) -> str:
if not Username or not re.fullmatch(r"[A-Za-z0-9_.-]+", Username):
raise ValueError(f"Invalid username: {Username}")
return Username
def GetDataDirectory() -> Path:
StorageConfig = Config.get("Storage", {})
RawPath = StorageConfig.get("DataDirectory", str(ApplicationDirectory / "Data"))
return Path(RawPath).expanduser().resolve()
def GetSettingsDirectory() -> Path:
return GetDataDirectory() / "Settings"
def GetSettingsPath(Username: str) -> Path:
Username = SanitizeUsernameForPath(Username)
return GetSettingsDirectory() / f"{Username}.toml"
def GetThemesDirectory() -> Path:
return GetDataDirectory() / "Themes"
def EnsureDataDirectory() -> None:
DataDirectory = GetDataDirectory()
ThemesDirectory = GetThemesDirectory()
DataDirectory.mkdir(parents=True, exist_ok=True)
GetSettingsDirectory().mkdir(parents=True, exist_ok=True)
ThemesDirectory.mkdir(parents=True, exist_ok=True)
if DefaultThemesDirectory.is_dir():
for ThemeFile in DefaultThemesDirectory.glob("*.toml"):
TargetPath = ThemesDirectory / ThemeFile.name
if not TargetPath.is_file():
shutil.copy(ThemeFile, TargetPath)
def LoadSettings(Username: str) -> dict[str, Any]:
EnsureDataDirectory()
SettingsPath = GetSettingsPath(Username)
if not SettingsPath.is_file():
with SettingsPath.open("wb") as SettingsFile:
tomli_w.dump(DefaultSettings, SettingsFile)
return dict(DefaultSettings)
with SettingsPath.open("rb") as SettingsFile:
Loaded = tomllib.load(SettingsFile)
return {**DefaultSettings, **Loaded}
def SaveSettings(Username: str, NewSettings: dict[str, Any]) -> dict[str, Any]:
EnsureDataDirectory()
SettingsPath = GetSettingsPath(Username)
Current: dict[str, Any] = {}
if SettingsPath.is_file():
with SettingsPath.open("rb") as SettingsFile:
Current = tomllib.load(SettingsFile)
Merged = {**DefaultSettings, **Current, **NewSettings}
with SettingsPath.open("wb") as SettingsFile:
tomli_w.dump(Merged, SettingsFile)
return Merged
def RenameUserSettings(OldUsername: str, NewUsername: str) -> None:
OldPath = GetSettingsPath(OldUsername)
if OldPath.is_file():
OldPath.rename(GetSettingsPath(NewUsername))
def ListThemeNames() -> list[str]:
EnsureDataDirectory()
return sorted(ThemeFile.stem for ThemeFile in GetThemesDirectory().glob("*.toml"))
def DeleteTheme(ThemeName: str) -> None:
EnsureDataDirectory()
ThemeName = SanitizeThemeName(ThemeName)
ThemePath = GetThemesDirectory() / f"{ThemeName}.toml"
if not ThemePath.is_file():
raise FileNotFoundError(f"Unknown theme: {ThemeName}")
ThemePath.unlink()
def LoadTheme(ThemeName: str) -> dict[str, str]:
EnsureDataDirectory()
ThemeName = SanitizeThemeName(ThemeName)
ThemePath = GetThemesDirectory() / f"{ThemeName}.toml"
if not ThemePath.is_file():
raise FileNotFoundError(f"Unknown theme: {ThemeName}")
with ThemePath.open("rb") as ThemeFile:
Loaded = tomllib.load(ThemeFile)
return Loaded.get("Palette", {})
def SaveTheme(ThemeName: str, Palette: dict[str, str]) -> None:
EnsureDataDirectory()
ThemeName = SanitizeThemeName(ThemeName)
MissingRoles = [Role for Role in PaletteRoles if Role not in Palette]
if MissingRoles:
FallbackPalette = LoadTheme("CatppuccinMocha")
for Role in MissingRoles:
Palette[Role] = FallbackPalette.get(Role, "#ff00ff")
ThemePath = GetThemesDirectory() / f"{ThemeName}.toml"
with ThemePath.open("wb") as ThemeFile:
tomli_w.dump({"Palette": Palette}, ThemeFile)