Files
DevOps/Kodi/Lenovo/addons/plugin.video.pseudotv.live/resources/lib/cqueue.py

207 lines
7.7 KiB
Python

# 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 <http://www.gnu.org/licenses/>.
#
# -*- 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()