from __future__ import annotations import os import sys from enum import Enum, auto from pathlib import Path from typing import Optional, overload from gi.repository import Gio, GObject class FileType(Enum): DIRECTORY = auto() VIDEO = auto() class FileItem(GObject.Object): file_type: FileType full_path: Path thumbnail: bytes _has_thumbnail: bool __gtype_name__ = "FileItem" def __init__(self, name: str, file_type: FileType, full_path: Path): super().__init__() self.name = name self.file_type = file_type self.full_path = full_path self.thumbnail = b"" self._has_thumbnail = False @GObject.Property(type=GObject.TYPE_UINT64) def saved_position(self) -> int: return self._load_attribute("position", 0) @saved_position.setter def set_saved_position(self, value: int): self._save_attribute("position", value if value else None) self.notify("saved-position") @GObject.Property(type=GObject.TYPE_UINT64) def saved_duration(self): return self._load_attribute("duration", 1) or 1 @saved_duration.setter def set_saved_duration(self, value: int): self._save_attribute("duration", value if value > 0 else None) self.notify("saved-duration") @GObject.Property(type=int) def saved_subtitle_track(self): return self._load_attribute("subtitle_track", -2) @saved_subtitle_track.setter def set_saved_subtitle_track(self, value: int): self._save_attribute("subtitle_track", value if value >= -1 else None) self.notify("saved-subtitle-track") @GObject.Property(type=int) def saved_audio_track(self): return self._load_attribute("audio_track", 0) @saved_audio_track.setter def set_saved_audio_track(self, value: int): self._save_attribute("audio_track", value if value > 0 else None) self.notify("saved-audio-track") @GObject.Property(type=bool, default=False) def has_thumbnail(self): return self._has_thumbnail @has_thumbnail.setter def set_has_thumbnail(self, value: bool): self._has_thumbnail = value self.notify("has-thumbnail") @overload def _load_attribute(self, name: str, dfl: str) -> str: ... @overload def _load_attribute(self, name: str, dfl: bytes) -> bytes: ... @overload def _load_attribute(self, name: str, dfl: int) -> int: ... def _load_attribute(self, name: str, dfl: str | bytes | int) -> str | bytes | int: try: strval = os.getxattr(self.full_path, f"user.lazy_player.{name}") return type(dfl)(strval) except OSError: return dfl def _save_attribute(self, name: str, value: str | bytes | float | int | None) -> None: try: if value is None: os.removexattr(self.full_path, f"user.lazy_player.{name}") else: if isinstance(value, bytes): os.setxattr(self.full_path, f"user.lazy_player.{name}", value) else: os.setxattr( self.full_path, f"user.lazy_player.{name}", str(value).encode("utf8") ) except OSError as err: print(err, file=sys.stderr) class FileListModel(GObject.Object, Gio.ListModel): """A ListModel implementation for FileItems""" __gtype_name__ = "FileListModel" items: list[FileItem] def __init__(self): super().__init__() self.items = [] def do_get_item_type(self) -> GObject.GType: return GObject.type_from_name("FileItem") def do_get_n_items(self) -> int: return len(self.items) def do_get_item(self, position: int) -> Optional[FileItem]: if 0 <= position < len(self.items): return self.items[position] return None def remove_all(self) -> None: removed = len(self.items) self.items = [] self.items_changed(0, removed, 0) def set_items(self, items: list[FileItem]): removed = len(self.items) self.items = list(items) self.items_changed(0, removed, len(self.items)) def append(self, item: FileItem) -> None: pos = len(self.items) self.items.append(item) self.items_changed(pos, 0, 1) def remove(self, position: int) -> None: if 0 <= position < len(self.items): self.items.pop(position) self.items_changed(position, 1, 0)