# Copyright (C) 2024 Lunatixz # # # This file is part of PseudoTV Live. # # PseudoTV Live 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. # # PseudoTV Live 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 PseudoTV Live. If not, see . # # -*- coding: utf-8 -*- from globals import * from pool import ExecutorPool from collections import defaultdict class LlNode: def __init__(self, package: tuple, priority: int=0, delay: int=0): self.prev = None self.next = None self.package = package self.priority = priority self.wait = delay class CustomQueue: isRunning = False def __init__(self, fifo: bool=False, lifo: bool=False, priority: bool=False, delay: bool=False, service=None): self.log("__init__, fifo = %s, lifo = %s, priority = %s, delay = %s"%(fifo, lifo, priority, delay)) self.service = service self.lock = Lock() self.fifo = fifo self.lifo = lifo self.priority = priority self.delay = delay self.head = None self.tail = None self.qsize = 0 self.min_heap = [] self.itemCount = defaultdict(int) self.popThread = Thread(target=self.__pop) self.pool = ExecutorPool() self.executor = SETTINGS.getSettingBool('Enable_Executors') def log(self, msg, level=xbmc.LOGDEBUG): return log('%s: %s'%(self.__class__.__name__,msg),level) def __manage(self, thread, target_function, name, daemon=True): if thread.is_alive(): if hasattr(thread, 'cancel'): thread.cancel() try: thread.join() except Exception: pass new_thread = Thread(target=target_function) new_thread.name = name new_thread.daemon = daemon new_thread.start() return new_thread def __start(self): self.log("__starting popThread") self.popThread = self.__manage(self.popThread, self.__pop, "popThread") def __run(self, func, *args, **kwargs): self.log(f"__run, func = {func.__name__}, executor = {self.executor}") try: if self.executor: return self.pool.executor(func, None, *args, **kwargs) else: thread = Thread(target=func, args=args, kwargs=kwargs) thread.start() except Exception as e: self.log(f"__run, func = {func.__name__} failed! {e}\nargs = {args}, kwargs = {kwargs}", xbmc.LOGERROR) def __exists(self, priority, package): for idx, item in enumerate(self.min_heap): epriority,_,epackage = item if package == epackage: if priority < epriority: try: self.min_heap.pop(idx) heapq.heapify(self.min_heap) # Ensure heap property is maintained self.log("__exists, replacing queue: func = %s, priority %s => %s"%(epackage[0].__name__,epriority,priority)) except: self.log("__exists, replacing queue: func = %s, idx = %s failed!"%(epackage[0].__name__,idx)) else: return True return False def _push(self, package: tuple, priority: int = 0, delay: int = 0): node = LlNode(package, priority, delay) if self.priority: if not self.__exists(priority, package): try: self.qsize += 1 self.itemCount[priority] += 1 self.log(f"_push, func = {package[0].__name__}, priority = {priority}") heapq.heappush(self.min_heap, (priority, self.itemCount[priority], package)) except Exception as e: self.log(f"_push, func = {package[0].__name__} failed! {e}", xbmc.LOGFATAL) else: if self.head: self.tail.next = node node.prev = self.tail self.tail = node else: self.head = node self.tail = node self.log(f"_push, func = {package[0].__name__}") if not self.isRunning: self.log("_push, starting __pop") self.__start() def __process(self, node, fifo=True): package = node.package self.log(f"process_node, package = {package}") next_node = node.__next__ if fifo else node.prev if next_node: next_node.prev = None if fifo else next_node.prev if node.prev: node.prev.next = None if fifo else node.prev if fifo: self.head = next_node else: self.tail = next_node return package def __pop(self): self.isRunning = True self.log("__pop, starting") self.executor = SETTINGS.getSettingBool('Enable_Executors') while not self.service.monitor.abortRequested(): if self.service.monitor.waitForAbort(0.0001): self.log("__pop, waitForAbort") break elif self.service._interrupt(): self.log("__pop, _interrupt") break elif self.service._suspend(): self.log("__pop, _suspend") self.service.monitor.waitForAbort(SUSPEND_TIMER) continue elif not self.head and not self.priority: self.log("__pop, The queue is empty!") break elif self.priority: if not self.min_heap: self.log("__pop, The priority queue is empty!") break else: try: priority, _, package = heapq.heappop(self.min_heap) except Exception as e: continue self.qsize -= 1 self.__run(package[0],*package[1],**package[2]) elif self.fifo or self.lifo: curr_node = self.head if self.fifo else self.tail if curr_node is None: break else: package = self.__process(curr_node, fifo=self.fifo) if not self.delay: self.__run(*package) else: timerit(curr_node.wait, [*package]) else: self.log("__pop, queue undefined!") break self.isRunning = False self.log("__pop, finished: shutting down!") # def quePriority(package: tuple, priority: int=0): # q_priority = CustomQueue(priority=True) # q_priority.log("quePriority") # q_priority._push(package, priority) # def queFIFO(package: tuple, delay: int=0): # q_fifo = CustomQueue(fifo=True, delay=bool(delay)) # q_fifo.log("queFIFO") # q_fifo._push(package, delay) # def queLIFO(package: tuple, delay: int=0): # q_lifo = CustomQueue(lifo=True, delay=bool(delay)) # q_lifo.log("queLIFO") # q_lifo._push(package, delay) # def queThread(packages, delay=0): # q_fifo = CustomQueue(fifo=True) # q_fifo.log("queThread") # def thread_function(*package): # q_fifo._push(package) # for package in packages: # t = Thread(target=thread_function, args=(package)) # t.daemon = True # t.start()