Source code for zooui.tilesystem.tileproviders.tileprovider

## ZooUI - Zooming User Interface
## Copyright (C) 2009 David Roberts <d@vidr.cc>
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 3
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, see <https://www.gnu.org/licenses/>.

"""Threaded class for loading tiles into memory (abstract base class)."""

from collections import deque
from threading import Condition, Event, Thread
from typing import TYPE_CHECKING, Any

from zooui.logger import get_logger
from zooui.tilesystem.tile import Tile

if TYPE_CHECKING:
    from zooui.tilesystem.tilestore.tilecache import TileCache

TileID = tuple[str, int, int, int]


[docs] class TileProvider(Thread): """ Constructor : TileProvider(tilecache) Parameters : tilecache : TileCache TileProvider(tilecache) --> TileProvider `TileProvider` objects are used for loading tiles into TileCache objects. This is an abstract base class that provides threaded infrastructure for loading tiles into memory. Subclasses must implement the `_load` method to define how tiles are actually loaded. Requests are processed in LIFO (Last In First Out) order. """ def __init__(self, tilecache: "TileCache") -> None: """ Method : TileProvider.__init__(tilecache) Parameters : tilecache : TileCache TileProvider.__init__(tilecache) --> None Create a new TileProvider for loading tiles into the given `tilecache`. Initializes the thread infrastructure, task queue, and logger for this tile provider instance. """ Thread.__init__(self) self.daemon = True self.__tilecache = tilecache self.__tasks: deque[TileID] = deque() self.__tasks_available = Condition() # Pause/resume mechanism self.__pause_event = Event() self.__pause_event.set() # Start in running (not paused) state # Shutdown event to signal the thread to stop self.__shutdown_event = Event() self._logger = get_logger(str(self))
[docs] def stop(self) -> None: """Signal the tile provider thread to stop and wake it if blocked.""" self.__shutdown_event.set() self.__pause_event.set() try: self.__tasks_available.acquire() self.__tasks_available.notify() finally: self.__tasks_available.release()
[docs] def request(self, tile_id: TileID) -> None: """ Method : TileProvider.request(tile_id) Parameters : tile_id : Tuple[str, int, int, int] TileProvider.request(tile_id) --> None Request the tile identified by `tile_id` be loaded into the tilecache. Requests are processed in a LIFO order. If the tile is unavailable, then None will be inserted into the tilecache to indicate this. """ self.__tasks_available.acquire() self.__tasks.append(tile_id) self.__tasks_available.notify() self.__tasks_available.release()
[docs] def _load(self, tile_id: TileID) -> Any | None: """ Method : TileProvider._load(tile_id) Parameters : tile_id : Tuple[str, int, int, int] TileProvider._load(tile_id) --> Image or None Load the requested tile, and return it as an `Image` object. Returns None if the tile does not exist. """ pass
[docs] def run(self) -> None: """ Method : TileProvider.run() Parameters : None TileProvider.run() --> None Run a loop to load requested tiles. """ while not self.__shutdown_event.is_set(): self.__pause_event.wait() if self.__shutdown_event.is_set(): break self.__tasks_available.acquire() while not self.__tasks and not self.__shutdown_event.is_set(): self.__tasks_available.wait() if self.__shutdown_event.is_set(): self.__tasks_available.release() break tile_id = self.__tasks.pop() self.__tasks_available.release() self.__pause_event.wait() if self.__shutdown_event.is_set(): break if tile_id not in self.__tilecache: try: tile = self._load(tile_id) except Exception: self._logger.exception("error loading tile") tile = None if tile is not None: self._logger.debug("loaded %s", str(tile_id)) self.__tilecache[tile_id] = Tile(tile) del tile else: self._logger.debug("unavailable %s", str(tile_id)) self.__tilecache[tile_id] = None
[docs] def purge(self, media_id: str | None = None) -> None: """ Method : TileProvider.purge(media_id) Parameters : media_id : Optional[str] TileProvider.purge(media_id) --> None Purge all tasks for the given `media_id`. All tasks will be purged if `media_id` is omitted. """ self.__tasks_available.acquire() self._logger.debug("purging %s", media_id or "all") if media_id: new_tasks: deque[TileID] = deque() for task in self.__tasks: if task[0] != media_id: new_tasks.append(task) self.__tasks = new_tasks else: self.__tasks = deque() self.__tasks_available.release()
[docs] def pause(self) -> None: """ Method : TileProvider.pause() Parameters : None TileProvider.pause() --> None Pause the tile provider thread. The thread will stop processing new tasks until resume() is called. """ self._logger.debug("pausing") self.__pause_event.clear()
[docs] def resume(self) -> None: """ Method : TileProvider.resume() Parameters : None TileProvider.resume() --> None Resume the tile provider thread after it was paused. """ self._logger.debug("resuming") self.__pause_event.set()
def __str__(self) -> str: """ Method : TileProvider.__str__() Parameters : None TileProvider.__str__() --> str Return a human-readable string representation of the TileProvider. """ return type(self).__name__ def __repr__(self) -> str: """ Method : TileProvider.__repr__() Parameters : None TileProvider.__repr__() --> str Return a formal string representation of the TileProvider. """ return f"{type(self).__name__}()"