# 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 cqueue import * from library import Library from autotune import Autotune from builder import Builder from backup import Backup from multiroom import Multiroom from server import HTTP class Tasks(): def __init__(self, service): self.service = service self.jsonRPC = service.jsonRPC self.player = service.player self.monitor = service.monitor self.cache = SETTINGS.cache self.quePriority = CustomQueue(priority=True,service=self.service) def log(self, msg, level=xbmc.LOGDEBUG): return log('%s: %s'%(self.__class__.__name__,msg),level) def _que(self, func, priority=-1, *args, **kwargs):# priority -1 autostack, 1 Highest, 5 Lowest if priority == -1: priority = self.quePriority.qsize + 1 self.log('_que, priority = %s, func = %s, args = %s, kwargs = %s' % (priority,func.__name__, args, kwargs)) self.quePriority._push((func, args, kwargs), priority) def _initialize(self): tasks = [self.chkInstanceID, self.chkSettings, self.chkDirs, self.chkWelcome, self.chkDebugging, self.chkBackup, self.chkHTTP, self.chkPVRBackend,] for func in tasks: self._que(func,1) self.log('_initialize, finished...') def chkSettings(self): self.service.currentSettings = dict(SETTINGS.getCurrentSettings()) self.log('chkSettings, currentSettings = %s'%(self.service.currentSettings)) def chkInstanceID(self): self.log('chkInstanceID') PROPERTIES.getInstanceID() @cacheit(expiration=datetime.timedelta(days=28), checksum=1) def chkWelcome(self): hasAutotuned = SETTINGS.hasAutotuned() self.log('chkWelcome, hasAutotuned = %s'%(hasAutotuned)) if not hasAutotuned: return BUILTIN.executescript('special://home/addons/%s/resources/lib/utilities.py, Show_Wiki_QR'%(ADDON_ID)) def chkDebugging(self): self.log('chkDebugging') if SETTINGS.getSettingBool('Debug_Enable'): if DIALOG.yesnoDialog(LANGUAGE(32142),autoclose=4): self.log('_chkDebugging, disabling debugging.') SETTINGS.setSettingBool('Debug_Enable',False) DIALOG.notificationDialog(LANGUAGE(32025)) self.jsonRPC.toggleShowLog(SETTINGS.getSettingBool('Debug_Enable')) def chkBackup(self): self.log('chkBackup') Backup().hasBackup() def chkHTTP(self): self.log('chkHTTP') HTTP(service=self.service) def chkServers(self): self.log('chkServers') Multiroom(service=self.service).chkServers() def chkPVRBackend(self): self.log('chkPVRBackend') if hasAddon(PVR_CLIENT_ID,True,True,True,True): if not SETTINGS.hasPVRInstance(): SETTINGS.setPVRPath(USER_LOC, SETTINGS.getFriendlyName()) def _chkQueTimer(self): self._chkEpochTimer('chkVersion' , self.chkVersion , 21600) self._chkEpochTimer('chkKodiSettings' , self.chkKodiSettings , 3600) self._chkEpochTimer('chkServers' , self.chkServers , 300) self._chkEpochTimer('chkDiscovery' , self.chkDiscovery , 300) self._chkEpochTimer('chkRecommended' , self.chkRecommended , 900) self._chkEpochTimer('chkLibrary' , self.chkLibrary , 3600) self._chkEpochTimer('chkFiles' , self.chkFiles , 300) self._chkEpochTimer('chkURLQUE' , self.chkURLQUE , 300) self._chkEpochTimer('chkJSONQUE' , self.chkJSONQUE , 300) self._chkEpochTimer('chkLOGOQUE' , self.chkLOGOQUE , 600) self._chkPropTimer('chkPVRRefresh' , self.chkPVRRefresh , 1) self._chkPropTimer('chkFillers' , self.chkFillers , 2) self._chkPropTimer('chkUpdate' , self.chkUpdate , 3) def _chkEpochTimer(self, key, func, runevery=900, priority=-1, nextrun=None, *args, **kwargs): if nextrun is None: nextrun = PROPERTIES.getPropertyInt(key, default=0) # nextrun == 0 => force que epoch = int(time.time()) if epoch >= nextrun: self.log('_chkEpochTimer, key = %s, last run %s' % (key, epoch - nextrun)) PROPERTIES.setPropertyInt(key, (epoch + runevery)) return self._que(func, priority, *args, **kwargs) def _chkPropTimer(self, key, func, priority=-1, *args, **kwargs): key = '%s.%s' % (ADDON_ID, key) if PROPERTIES.getEXTPropertyBool(key): self.log('_chkPropTimer, key = %s' % (key)) PROPERTIES.clrEXTProperty(key) self._que(func, priority, *args, **kwargs) @cacheit(expiration=datetime.timedelta(minutes=10)) def getOnlineVersion(self): try: ONLINE_VERSION = re.compile('" version="(.+?)" name="%s"'%(ADDON_NAME)).findall(str(requestURL(ADDON_URL)))[0] except: ONLINE_VERSION = ADDON_VERSION return ONLINE_VERSION def chkVersion(self): update = False ONLINE_VERSION = self.getOnlineVersion() if ADDON_VERSION < ONLINE_VERSION: update = True DIALOG.notificationDialog(LANGUAGE(30073)%(ONLINE_VERSION)) elif ADDON_VERSION > (SETTINGS.getCacheSetting('lastVersion', checksum=ADDON_VERSION) or '0.0.0'): SETTINGS.setCacheSetting('lastVersion',ADDON_VERSION, checksum=ADDON_VERSION) BUILTIN.executescript('special://home/addons/%s/resources/lib/utilities.py, Show_Changelog'%(ADDON_ID)) self.log('chkVersion, update = %s, installed version = %s, online version = %s'%(update,ADDON_VERSION,ONLINE_VERSION)) SETTINGS.setSetting('Update_Status',{'True':'[COLOR=yellow]%s [B]v.%s[/B][/COLOR]'%(LANGUAGE(32168),ONLINE_VERSION),'False':'None'}[str(update)]) def chkKodiSettings(self): self.log('chkKodiSettings') MIN_GUIDEDAYS = SETTINGS.setSettingInt('Min_Days' ,self.jsonRPC.getSettingValue('epg.pastdaystodisplay' ,default=1)) MAX_GUIDEDAYS = SETTINGS.setSettingInt('Max_Days' ,self.jsonRPC.getSettingValue('epg.futuredaystodisplay' ,default=3)) OSD_TIMER = SETTINGS.setSettingInt('OSD_Timer',self.jsonRPC.getSettingValue('pvrmenu.displaychannelinfo',default=5)) def chkDirs(self): self.log('chkDirs') [FileAccess.makedirs(folder) for folder in [LOGO_LOC,FILLER_LOC,TEMP_LOC] if not FileAccess.exists(os.path.join(folder,''))] def chkFiles(self): self.log('chkFiles') self.chkDirs() if not (FileAccess.exists(LIBRARYFLEPATH) & FileAccess.exists(CHANNELFLEPATH) & FileAccess.exists(M3UFLEPATH) & FileAccess.exists(XMLTVFLEPATH) & FileAccess.exists(GENREFLEPATH)): self._que(self.chkLibrary,2) def chkDiscovery(self): self.log('chkDiscovery') timerit(Multiroom(service=self.service)._chkDiscovery)(1.0) def chkRecommended(self): self.log('chkRecommended') try: library = Library(service=self.service) library.searchRecommended() del library except Exception as e: self.log('chkRecommended failed! %s'%(e), xbmc.LOGERROR) def chkLibrary(self, force=PROPERTIES.getPropertyBool('ForceLibrary')): try: library = Library(service=self.service) library.importPrompt() #refactor feature complete = library.updateLibrary(force) del library if complete: self._que(self.chkChannels,3) if force: PROPERTIES.setPropertyBool('ForceLibrary',False) else: self._que(self.chkLibrary,2,force) self.log('chkLibrary, force = %s, complete = %s'%(force,complete)) except Exception as e: self.log('chkLibrary failed! %s'%(e), xbmc.LOGERROR) def chkUpdate(self): ids = PROPERTIES.getUpdateChannels() if ids: channels = self.getVerifiedChannels() channels = [citem for id in ids for citem in channels if citem.get('id') == id] self.log('chkUpdate, channels = %s\nid = %s'%(len(channels),ids)) self._que(self.chkChannels,3,channels) def chkChannels(self, channels: list=[]): save = False complete = False builder = Builder(service=self.service) hasAutotuned = SETTINGS.hasAutotuned() hasFirstRun = PROPERTIES.hasFirstRun() hasEnabledServers = PROPERTIES.hasEnabledServers() if not channels: save = True channels = builder.getVerifiedChannels() SETTINGS.setSetting('Select_Channels','[B]%s[/B] Channels'%(len(channels))) PROPERTIES.setChannels(len(channels) > 0) self.service.currentChannels = channels #update service channels if len(channels) > 0: if not hasAutotuned: SETTINGS.setAutotuned(complete) complete, updated = builder.build(channels) self.log('chkChannels, channels = %s, complete = %s, updated = %s'%(len(channels),complete,updated)) if complete: if save: builder.channels.setChannels(channels) if updated: PROPERTIES.setPropTimer('chkPVRRefresh') if SETTINGS.getSettingBool('Build_Filler_Folders'): self._que(self.chkFillers,2,channels) else: self._que(self.chkChannels,3,channels) elif not hasAutotuned: return SETTINGS.setAutotuned(Autotune()._runTune()) elif hasEnabledServers: return PROPERTIES.setPropTimer('chkPVRRefresh') del builder if not hasFirstRun: PROPERTIES.setFirstRun(complete) def chkLOGOQUE(self): if not PROPERTIES.isRunning('chkLOGOQUE') and PROPERTIES.hasFirstRun(): with PROPERTIES.chkRunning('chkLOGOQUE'): updated = False library = Library(service=self.service) resources = library.resources queuePool = (SETTINGS.getCacheSetting('queueLOGO', json_data=True) or {}) params = randomShuffle(queuePool.get('params',[])) for i in list(range(QUEUE_CHUNK)): if self.service._interrupt(): self.log("chkLOGOQUE, _interrupt") break elif len(params) > 0: param = params.pop(0) updated = True self.log("chkLOGOQUE, queuing = %s\n%s"%(len(params),param)) if param.get('name','').startswith('getLogoResources'): self._que(resources.getLogoResources, 10+i, *param.get('args',()), **param.get('kwargs',{})) elif param.get('name','').startswith('getTVShowLogo'): self._que(resources.getTVShowLogo, 10+i, *param.get('args',()), **param.get('kwargs',{})) queuePool['params'] = setDictLST(params) if updated and len(queuePool['params']) == 0: PROPERTIES.setPropertyBool('ForceLibrary',True) self.log('chkLOGOQUE, remaining = %s'%(len(queuePool['params']))) SETTINGS.setCacheSetting('queueLOGO', queuePool, json_data=True) del library def chkJSONQUE(self): if not PROPERTIES.isRunning('chkJSONQUE') and PROPERTIES.hasFirstRun(): with PROPERTIES.chkRunning('chkJSONQUE'): queuePool = (SETTINGS.getCacheSetting('queueJSON', json_data=True) or {}) params = queuePool.get('params',[]) for i in list(range(QUEUE_CHUNK)): if self.service._interrupt(): self.log("chkJSONQUE, _interrupt") break elif len(params) > 0: param = params.pop(0) self.log("chkJSONQUE, queuing = %s\n%s"%(len(params),param)) self._que(self.jsonRPC.sendJSON,5+1, param) queuePool['params'] = setDictLST(params) self.log('chkJSONQUE, remaining = %s'%(len(queuePool['params']))) SETTINGS.setCacheSetting('queueJSON', queuePool, json_data=True) def chkURLQUE(self): if not PROPERTIES.isRunning('chkURLQUE') and PROPERTIES.hasFirstRun(): with PROPERTIES.chkRunning('chkURLQUE'): queuePool = (SETTINGS.getCacheSetting('queueURL', json_data=True) or {}) params = queuePool.get('params',[]) for i in list(range(QUEUE_CHUNK)): if self.service._interrupt(): self.log("chkURLQUE, _interrupt") break elif len(params) > 0: param = params.pop(0) self.log("chkURLQUE, queuing = %s\n%s"%(len(params),param)) self._que(requestURL,1, param) queuePool['params'] = setDictLST(params) self.log('chkURLQUE, remaining = %s'%(len(queuePool['params']))) SETTINGS.setCacheSetting('queueURL', queuePool, json_data=True) def chkPVRRefresh(self): self.log('chkPVRRefresh') self._que(self.chkPVRToggle,1) def chkPVRToggle(self): if self.service.monitor.isIdle and not (self.player.isPlaying() | BUILTIN.isScanning() | BUILTIN.isRecording()): togglePVR(False,True) else: timerit(PROPERTIES.setPropTimer)(FIFTEEN,['chkPVRRefresh']) def chkFillers(self, channels=[]): self.log('chkFillers') if not channels: channels = self.getVerifiedChannels() pDialog = DIALOG.progressBGDialog(header='%s, %s'%(ADDON_NAME,LANGUAGE(32179))) for idx, ftype in enumerate(FILLER_TYPES): if not FileAccess.exists(os.path.join(FILLER_LOC,ftype.lower(),'')): pDialog = DIALOG.progressBGDialog(int(idx*50//len(ftype)), pDialog, message='%s: %s'%(ftype,int(idx*100//len(ftype)))+'%', header='%s, %s'%(ADDON_NAME,LANGUAGE(32179))) FileAccess.makedirs(os.path.join(FILLER_LOC,ftype.lower(),'')) genres = self.getGenreNames() for idx, citem in enumerate(channels): for ftype in FILLER_TYPES[1:]: for genre in genres: if not FileAccess.exists(os.path.join(FILLER_LOC,ftype.lower(),genre.lower(),'')): pDialog = DIALOG.progressBGDialog(int(idx*50//len(channels)), pDialog, message='%s: %s'%(genre,int(idx*100//len(channels)))+'%', header='%s, %s'%(ADDON_NAME,LANGUAGE(32179))) FileAccess.makedirs(os.path.join(FILLER_LOC,ftype.lower(),genre.lower())) if not FileAccess.exists(os.path.join(FILLER_LOC,ftype.lower(),citem.get('name','').lower())): if ftype.lower() == 'adverts': IGNORE = IGNORE_CHTYPE + MOVIE_CHTYPE else: IGNORE = IGNORE_CHTYPE if citem.get('name') and not citem.get('radio',False) and citem.get('type') not in IGNORE: pDialog = DIALOG.progressBGDialog(int(idx*50//len(channels)), pDialog, message='%s: %s'%(citem.get('name'),int(idx*100//len(channels)))+'%', header='%s, %s'%(ADDON_NAME,LANGUAGE(32179))) FileAccess.makedirs(os.path.join(FILLER_LOC,ftype.lower(),citem['name'].lower())) pDialog = DIALOG.progressBGDialog(100, pDialog, message=LANGUAGE(32025), header='%s, %s'%(ADDON_NAME,LANGUAGE(32179))) @cacheit(expiration=datetime.timedelta(minutes=15),json_data=False) def getGenreNames(self): self.log('getGenres') try: library = Library(self.service) tvgenres = library.getTVGenres() moviegenres = library.getMovieGenres() genres = set([tvgenre.get('name') for tvgenre in tvgenres if tvgenre.get('name')] + [movgenre.get('name') for movgenre in moviegenres if movgenre.get('name')]) del library return list(genres) except Exception as e: self.log('getGenres failed! %s'%(e), xbmc.LOGERROR) return [] def chkSettingsChange(self, settings={}): nSettings = dict(SETTINGS.getCurrentSettings()) for setting, value in list(settings.items()): actions = {'User_Folder' :{'func':self.setUserPath ,'kwargs':{'old':value,'new':nSettings.get(setting)}}, 'Debug_Enable' :{'func':self.jsonRPC.toggleShowLog ,'kwargs':{'state':SETTINGS.getSettingBool('Debug_Enable')}}, 'Overlay_Enable' :{'func':PROPERTIES.setPendingRestart,'kwargs':{}}, 'Enable_OnInfo' :{'func':PROPERTIES.setPendingRestart,'kwargs':{}}, 'Disable_Trakt' :{'func':PROPERTIES.setPendingRestart,'kwargs':{}}, 'Rollback_Watched' :{'func':PROPERTIES.setPendingRestart,'kwargs':{}}, 'Store_Duration' :{'func':PROPERTIES.setPendingRestart,'kwargs':{}}, 'Seek_Tolerance' :{'func':PROPERTIES.setPendingRestart,'kwargs':{}}, 'Seek_Threshold' :{'func':PROPERTIES.setPendingRestart,'kwargs':{}}, 'Idle_Timer' :{'func':PROPERTIES.setPendingRestart,'kwargs':{}}, 'Run_While_Playing' :{'func':PROPERTIES.setPendingRestart,'kwargs':{}}, 'Restart_Percentage':{'func':PROPERTIES.setPendingRestart,'kwargs':{}},} if nSettings.get(setting) != value and actions.get(setting): action = actions.get(setting) self.log('chkSettingsChange, detected change in %s: %s => %s\naction = %s'%(setting,value,nSettings.get(setting),action)) self._que(action.get('func'),1,*action.get('args',()),**action.get('kwargs',{})) return nSettings def setUserPath(self, old, new): with PROPERTIES.interruptActivity(): self.log('setUserPath, old = %s, new = %s'%(old,new)) dia = DIALOG.progressDialog(message='%s\n%s'%(LANGUAGE(32050),old)) FileAccess.copyFolder(old, new, dia) SETTINGS.setPVRPath(new,prompt=True,force=True) PROPERTIES.setPendingRestart() DIALOG.progressDialog(100, dia) def getVerifiedChannels(self): return Builder(service=self.service).getVerifiedChannels()