# Copyright 2022 (c) Anna Schumaker """Idle queues to assid with large database operations.""" import typing from gi.repository import GObject from gi.repository import GLib class Queue(GObject.GObject): """A base class Idle Queue.""" total = GObject.Property(type=int) progress = GObject.Property(type=float) running = GObject.Property(type=bool, default=False) enabled = GObject.Property(type=bool, default=True) def __init__(self, **kwargs): """Initialize an Idle Queue.""" super().__init__(**kwargs) self._tasks = [] self._idle_id = None def __getitem__(self, n: int) -> tuple: """Get the n-th task in the queue.""" return self._tasks[n] if n < len(self._tasks) else None def __run_next_task(self) -> None: task = self._tasks[0] if task[0](*task[1:]): self._tasks.pop(0) def __start(self) -> None: if not self.running: self.running = True self._idle_id = GLib.idle_add(self.run_task) self.__update_counters() def __update_counters(self) -> bool: if (pending := len(self._tasks)) == 0: self.cancel() return GLib.SOURCE_REMOVE self.progress = 1 - (pending / self.total) return GLib.SOURCE_CONTINUE def cancel(self) -> None: """Cancel all pending tasks.""" if self._idle_id is not None: GLib.source_remove(self._idle_id) self._tasks.clear() self.progress = 0.0 self.total = 0 self.running = False self._idle_id = None def cancel_task(self, func: typing.Callable) -> None: """Remove all instances of a specific task from the Idle Queue.""" self._tasks = [t for t in self._tasks if t[0] != func] self.__update_counters() def complete(self) -> None: """Complete all pending tasks.""" if self.running: while len(self._tasks) > 0: self.__run_next_task() self.cancel() def push(self, func: typing.Callable, *args, now: bool = False, first: bool = False) -> bool | None: """Add a task to the Idle Queue.""" if not self.enabled or now: return func(*args) pos = 0 if first else len(self._tasks) self._tasks.insert(pos, (func, *args)) self.total += 1 self.__start() def push_many(self, func: typing.Callable, args: list[tuple[any]], now: bool = False) -> None: """Add several tasks to the Idle Queue.""" if not self.enabled or now: for arg in args: func(*arg) else: self._tasks.extend([(func, *arg) for arg in args]) self.total += len(args) self.__start() def run_task(self) -> bool: """Manually run the next task.""" if len(self._tasks) > 0: self.__run_next_task() return self.__update_counters() return GLib.SOURCE_REMOVE