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 from .thumbnailer import Thumbnailer class FileType(Enum): DIRECTORY = auto() VIDEO = auto() class FileItem(GObject.Object): file_type: FileType full_path: Path thumbnail: bytes attempted_thumbnail: bool _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.attempted_thumbnail = False 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") def ensure_thumbnail(self, thumbnailer: Thumbnailer): if self.thumbnail or self.attempted_thumbnail: return if not self.attempted_thumbnail: thumbnailer.generate_thumbnail(self) @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)