mirror of
https://git.srhtnin.garden/sarah/SaraFem.git
synced 2026-10-13 18:18:11 +02:00
96 lines
3.5 KiB
Python
96 lines
3.5 KiB
Python
"""Sidebar bookmarks, stored in ~/.config/SaraFem/.sfem_bookmarks.toml."""
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
import toml
|
|
|
|
from Core import BookmarksPath, ConfigDir, IsWindows
|
|
|
|
DefaultIcon = "BOOKMARK_OUTLINE"
|
|
# The freedesktop trash folder (Linux). Windows' Recycle Bin isn't a folder
|
|
# that can be browsed, so there's no Trash bookmark there (deleting still goes
|
|
# to the Recycle Bin, through send2trash).
|
|
TrashPath = None if IsWindows else Path.home() / ".local" / "share" / "Trash" / "files"
|
|
|
|
# One-time marker: without it, re-adding the Trash bookmark to old files (see
|
|
# LoadBookmarks) would run on every load and undo the user removing it.
|
|
_TrashMigrationMarker = ConfigDir / ".trash_bookmark_migrated"
|
|
|
|
# A small curated set for the icon picker, rather than the whole Material set.
|
|
IconChoices = [
|
|
"BOOKMARK_OUTLINE", "HOME_OUTLINED", "FOLDER_OUTLINED", "DESCRIPTION_OUTLINED",
|
|
"DOWNLOAD_OUTLINED", "IMAGE_OUTLINED", "MUSIC_NOTE_OUTLINED", "VIDEOCAM_OUTLINED",
|
|
"CODE_OUTLINED", "STAR_OUTLINE", "FAVORITE_OUTLINE", "WORK_OUTLINE", "DNS_OUTLINED",
|
|
]
|
|
|
|
# The dropdown label is normally derived from the icon name itself
|
|
# (Name.split("_")[0].title()) — DNS_OUTLINED is Material's stock "server
|
|
# rack" glyph, but "Dns" reads as nothing to most people, so it gets an
|
|
# explicit label instead.
|
|
IconLabelOverrides = {"DNS_OUTLINED": "Server"}
|
|
|
|
|
|
@dataclass
|
|
class Bookmark:
|
|
Name: str
|
|
Path: Path
|
|
Icon: str = DefaultIcon
|
|
|
|
|
|
def DefaultBookmarks() -> list[Bookmark]:
|
|
return [
|
|
Bookmark("Home", Path.home(), "HOME_OUTLINED"),
|
|
Bookmark("Documents", Path.home() / "Documents", "DESCRIPTION_OUTLINED"),
|
|
Bookmark("Downloads", Path.home() / "Downloads", "DOWNLOAD_OUTLINED"),
|
|
Bookmark("Pictures", Path.home() / "Pictures", "IMAGE_OUTLINED"),
|
|
*([Bookmark("Trash", TrashPath, "DELETE_OUTLINE")] if TrashPath else []),
|
|
]
|
|
|
|
|
|
def SaveBookmarks(Bookmarks: list[Bookmark]) -> None:
|
|
ConfigDir.mkdir(parents=True, exist_ok=True)
|
|
Data = {"Bookmark": [{"Name": B.Name, "Path": str(B.Path), "Icon": B.Icon} for B in Bookmarks]}
|
|
BookmarksPath.write_text(toml.dumps(Data))
|
|
|
|
|
|
def LoadBookmarks() -> list[Bookmark]:
|
|
if not BookmarksPath.exists():
|
|
Bookmarks = DefaultBookmarks()
|
|
SaveBookmarks(Bookmarks)
|
|
return Bookmarks
|
|
|
|
try:
|
|
Data = toml.loads(BookmarksPath.read_text())
|
|
except toml.TomlDecodeError:
|
|
return DefaultBookmarks()
|
|
|
|
Entries = Data.get("Bookmark", [])
|
|
Bookmarks = [
|
|
Bookmark(Entry["Name"], Path(Entry["Path"]), Entry.get("Icon", DefaultIcon))
|
|
for Entry in Entries
|
|
if "Name" in Entry and "Path" in Entry
|
|
]
|
|
if not Bookmarks:
|
|
return DefaultBookmarks()
|
|
|
|
# Trash used to be a hardcoded Sidebar entry, not a real bookmark (so it
|
|
# couldn't be edited). Migrate existing bookmark files that predate that —
|
|
# but only once, so deliberately removing Trash afterwards actually sticks.
|
|
if TrashPath is not None and not _TrashMigrationMarker.exists():
|
|
if not any(B.Path == TrashPath for B in Bookmarks):
|
|
Bookmarks.append(Bookmark("Trash", TrashPath, "DELETE_OUTLINE"))
|
|
SaveBookmarks(Bookmarks)
|
|
ConfigDir.mkdir(parents=True, exist_ok=True)
|
|
_TrashMigrationMarker.touch()
|
|
|
|
return Bookmarks
|
|
|
|
|
|
def AddBookmark(Name: str, TargetPath: Path, Icon: str = DefaultIcon) -> None:
|
|
Bookmarks = LoadBookmarks()
|
|
if any(B.Path == TargetPath for B in Bookmarks):
|
|
return # already bookmarked
|
|
Bookmarks.append(Bookmark(Name, TargetPath, Icon))
|
|
SaveBookmarks(Bookmarks)
|