974 lines
52 KiB
Python
974 lines
52 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 cache import Cache
|
|
from channels import Channels
|
|
from jsonrpc import JSONRPC
|
|
from rules import RulesList
|
|
from resources import Resources
|
|
from xsp import XSP
|
|
from infotagger.listitem import ListItemInfoTag
|
|
|
|
# Actions
|
|
ACTION_MOVE_LEFT = 1
|
|
ACTION_MOVE_RIGHT = 2
|
|
ACTION_MOVE_UP = 3
|
|
ACTION_MOVE_DOWN = 4
|
|
ACTION_SELECT_ITEM = 7
|
|
ACTION_INVALID = 999
|
|
ACTION_SHOW_INFO = [11,24,401]
|
|
ACTION_PREVIOUS_MENU = [92, 10,110,521] #+ [9, 92, 216, 247, 257, 275, 61467, 61448]
|
|
|
|
class Manager(xbmcgui.WindowXMLDialog):
|
|
monitor = MONITOR()
|
|
focusIndex = -1
|
|
newChannels = []
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
|
|
|
|
def __get1stChannel(channelList):
|
|
for channel in channelList:
|
|
if not channel.get('id'): return channel.get('number')
|
|
return 1
|
|
|
|
|
|
def __findChannel(chnum, retitem=False, channels=[]):
|
|
for idx, channel in enumerate(channels):
|
|
if channel.get('number') == (chnum or 1):
|
|
if retitem: return channel
|
|
else: return idx
|
|
|
|
with BUILTIN.busy_dialog():
|
|
self.server = {}
|
|
self.lockAutotune = True
|
|
self.madeChanges = False
|
|
self.madeItemchange = False
|
|
self.lastActionTime = time.time()
|
|
self.cntrlStates = {}
|
|
self.showingList = True
|
|
self.startChannel = kwargs.get('channel',-1)
|
|
self.openChannel = kwargs.get('open')
|
|
|
|
self.cache = SETTINGS.cache
|
|
self.channels = Channels()
|
|
self.rule = RulesList()
|
|
self.jsonRPC = JSONRPC()
|
|
self.resource = Resources()
|
|
|
|
self.host = PROPERTIES.getRemoteHost()
|
|
self.friendly = SETTINGS.getFriendlyName()
|
|
self.newChannel = self.channels.getTemplate()
|
|
self.eChannels = self.loadChannels(SETTINGS.getSetting('Default_Channels'))
|
|
|
|
try:
|
|
if self.eChannels is None: raise Exception("No Channels Found!")
|
|
else:
|
|
self.channelList = self.channels.sortChannels(self.createChannelList(self.buildArray(), self.eChannels))
|
|
self.newChannels = self.channelList.copy()
|
|
|
|
if self.startChannel == -1: self.startChannel = __get1stChannel(self.channelList)
|
|
if self.startChannel <= CHANNEL_LIMIT: self.focusIndex = (self.startChannel - 1) #Convert from Channel number to array index
|
|
else: self.focusIndex = __findChannel(self.startChannel,channels=self.channelList)
|
|
if self.openChannel: self.openChannel = self.channelList[self.focusIndex]
|
|
self.log('Manager, startChannel = %s, focusIndex = %s, openChannel = %s'%(self.startChannel, self.focusIndex, self.openChannel))
|
|
|
|
if kwargs.get('start',True): self.doModal()
|
|
except Exception as e:
|
|
self.log('Manager failed! %s'%(e), xbmc.LOGERROR)
|
|
self.closeManager()
|
|
|
|
|
|
def log(self, msg, level=xbmc.LOGDEBUG):
|
|
return log('%s: %s'%(self.__class__.__name__,msg),level)
|
|
|
|
|
|
def onInit(self):
|
|
try:
|
|
self.focusItems = dict()
|
|
self.spinner = self.getControl(4)
|
|
self.chanList = self.getControl(5)
|
|
self.itemList = self.getControl(6)
|
|
self.right_button1 = self.getControl(9001)
|
|
self.right_button2 = self.getControl(9002)
|
|
self.right_button3 = self.getControl(9003)
|
|
self.right_button4 = self.getControl(9004)
|
|
self.fillChanList(self.newChannels,focus=self.focusIndex,channel=self.openChannel)
|
|
except Exception as e:
|
|
log("onInit, failed! %s"%(e), xbmc.LOGERROR)
|
|
self.closeManager()
|
|
|
|
|
|
def getServers(self):
|
|
from multiroom import Multiroom
|
|
return Multiroom().getDiscovery()
|
|
|
|
|
|
def loadChannels(self, name=''):
|
|
self.log('loadChannels, name = %s'%(name))
|
|
channels = self.channels.getChannels()
|
|
if name == self.friendly: return channels
|
|
elif name == LANGUAGE(30022):#Auto
|
|
if len(channels) > 0: return channels
|
|
else: return self.loadChannels('Ask')
|
|
elif name == 'Ask':
|
|
def __buildItem(servers, server):
|
|
if server.get('online',False):
|
|
return self.buildListItem(server.get('name'),'%s - %s: Channels (%s)'%(LANGUAGE(32211)%({True:'green',False:'red'}[server.get('online',False)],{True:LANGUAGE(32158),False:LANGUAGE(32253)}[server.get('online',False)]),server.get('host'),len(server.get('channels',[]))),icon=DUMMY_ICON.format(text=str(servers.index(server)+1)))
|
|
servers = self.getServers()
|
|
lizlst = poolit(__buildItem)(*(list(servers.values()),list(servers.values())))
|
|
lizlst.insert(0,self.buildListItem(self.friendly,'%s - %s: Channels (%s)'%('[B]Local[/B]',self.host,len(channels)),icon=ICON))
|
|
select = DIALOG.selectDialog(lizlst, LANGUAGE(30173), None, True, SELECT_DELAY, False)
|
|
if not select is None: return self.loadChannels(lizlst[select].getLabel())
|
|
else: return
|
|
elif name:
|
|
self.server = self.getServers().get(name,{})
|
|
return self.server.get('channels',[])
|
|
return channels
|
|
|
|
|
|
@cacheit(json_data=True)
|
|
def buildArray(self):
|
|
self.log('buildArray') # Create blank array of citem templates.
|
|
def __create(idx):
|
|
newChannel = self.newChannel.copy()
|
|
newChannel['number'] = idx + 1
|
|
return newChannel
|
|
return poolit(__create)(list(range(CHANNEL_LIMIT)))
|
|
|
|
|
|
def createChannelList(self, channelArray, channelList):
|
|
self.log('createChannelList') # Fill blank array with citems from channels.json
|
|
def __update(item):
|
|
try: channelArray[item["number"]-1].update(item) #CUSTOM
|
|
except: channelArray.append(item) #AUTOTUNE
|
|
|
|
checksum = getMD5(dumpJSON(channelList))
|
|
cacheName = 'createChannelList.%s'%(checksum)
|
|
cacheResponse = self.cache.get(cacheName, checksum=checksum, json_data=True)
|
|
if not cacheResponse:
|
|
poolit(__update)(channelList)
|
|
cacheResponse = self.cache.set(cacheName, channelArray, checksum=checksum, json_data=True)
|
|
return cacheResponse
|
|
|
|
|
|
def buildListItem(self, label: str="", label2: str="", icon: str="", paths: list=[], items: dict={}):
|
|
if not icon: icon = (items.get('citem',{}).get('logo') or COLOR_LOGO)
|
|
if not paths: paths = (items.get('citem',{}).get("path") or [])
|
|
return LISTITEMS.buildMenuListItem(label, label2, icon, url='|'.join(paths), props=items)
|
|
|
|
|
|
def fillChanList(self, channelList, refresh=False, focus=None, channel=None):
|
|
self.log('fillChanList, focus = %s, channel = %s'%(focus,channel))
|
|
def __buildItem(citem):
|
|
isPredefined = citem["number"] > CHANNEL_LIMIT
|
|
isFavorite = citem.get('favorite',False)
|
|
isRadio = citem.get('radio',False)
|
|
isLocked = isPredefined #todo parse channel lock rule
|
|
channelColor = COLOR_UNAVAILABLE_CHANNEL
|
|
labelColor = COLOR_UNAVAILABLE_CHANNEL
|
|
|
|
if citem.get("path"):
|
|
if isPredefined: channelColor = COLOR_LOCKED_CHANNEL
|
|
else:
|
|
labelColor = COLOR_AVAILABLE_CHANNEL
|
|
if isLocked: channelColor = COLOR_LOCKED_CHANNEL
|
|
elif isFavorite: channelColor = COLOR_FAVORITE_CHANNEL
|
|
elif isRadio: channelColor = COLOR_RADIO_CHANNEL
|
|
else: channelColor = COLOR_AVAILABLE_CHANNEL
|
|
return self.buildListItem('[COLOR=%s][B]%s|[/COLOR][/B]'%(channelColor,citem["number"]),'[COLOR=%s]%s[/COLOR]'%(labelColor,citem.get("name",'')),items={'citem':citem,'chname':citem["name"],'chnum':'%i'%(citem["number"]),'radio':citem.get('radio',False),'description':LANGUAGE(32169)%(citem["number"],self.server.get('name',self.friendly))})
|
|
|
|
self.togglechanList(reset=refresh)
|
|
with self.toggleSpinner():
|
|
listitems = poolit(__buildItem)(channelList)
|
|
self.chanList.addItems(listitems)
|
|
if focus is None: self.chanList.selectItem(self.setFocusPOS(listitems))
|
|
else: self.chanList.selectItem(focus)
|
|
self.setFocus(self.chanList)
|
|
if channel: self.buildChannelItem(channel)
|
|
|
|
|
|
@contextmanager
|
|
def toggleSpinner(self, state=True, allow=True):
|
|
if allow:
|
|
self.setVisibility(self.spinner,state)
|
|
try: yield
|
|
finally: self.setVisibility(self.spinner,False)
|
|
else: yield
|
|
|
|
|
|
def togglechanList(self, state=True, focus=0, reset=False):
|
|
self.log('togglechanList, state = %s, focus = %s, reset = %s'%(state,focus,reset))
|
|
with self.toggleSpinner():
|
|
if state: # channellist
|
|
if reset:
|
|
self.setVisibility(self.chanList,False)
|
|
self.chanList.reset()
|
|
|
|
self.setVisibility(self.itemList,False)
|
|
self.setVisibility(self.chanList,True)
|
|
self.setFocus(self.chanList)
|
|
self.chanList.selectItem(focus)
|
|
|
|
if self.madeChanges:
|
|
self.setLabels(self.right_button1,LANGUAGE(32059))#Save
|
|
self.setLabels(self.right_button2,LANGUAGE(32060))#Cancel
|
|
self.setLabels(self.right_button3,LANGUAGE(32136))#Move
|
|
self.setLabels(self.right_button4,LANGUAGE(32061))#Delete
|
|
self.setEnableCondition(self.right_button1,'[!String.IsEmpty(Container(5).ListItem(Container(5).Position).Property(chnum))]')
|
|
self.setEnableCondition(self.right_button2,'[!String.IsEmpty(Container(5).ListItem(Container(5).Position).Property(chnum))]')
|
|
else:
|
|
self.setLabels(self.right_button1,LANGUAGE(32062))#Close
|
|
self.setLabels(self.right_button2,LANGUAGE(32235))#Preview
|
|
self.setLabels(self.right_button3,LANGUAGE(32136))#Move
|
|
self.setLabels(self.right_button4,LANGUAGE(32061))#Delete
|
|
self.setEnableCondition(self.right_button1,'[!String.IsEmpty(Container(5).ListItem(Container(5).Position).Property(chnum))]')
|
|
self.setEnableCondition(self.right_button2,'[!String.IsEmpty(Container(5).ListItem(Container(5).Position).Path) + String.IsEqual(Container(5).ListItem(Container(5).Position).Property(radio),False)]')
|
|
|
|
self.setFocus(self.right_button1)
|
|
self.setEnableCondition(self.right_button3,'[!String.IsEmpty(Container(5).ListItem(Container(5).Position).Path)]')# + Integer.IsLessOrEqual(Container(5).ListItem(Container(5).Position).Property(chnum),CHANNEL_LIMIT)]')
|
|
self.setEnableCondition(self.right_button4,'[!String.IsEmpty(Container(5).ListItem(Container(5).Position).Path)]')# + Integer.IsLessOrEqual(Container(5).ListItem(Container(5).Position).Property(chnum),CHANNEL_LIMIT)]')
|
|
else: # channelitems
|
|
self.itemList.reset()
|
|
self.setVisibility(self.chanList,False)
|
|
self.setVisibility(self.itemList,True)
|
|
self.itemList.selectItem(focus)
|
|
self.setFocus(self.itemList)
|
|
|
|
if self.madeItemchange:
|
|
self.setLabels(self.right_button1,LANGUAGE(32240))#Confirm
|
|
self.setLabels(self.right_button2,LANGUAGE(32060))#Cancel
|
|
self.setEnableCondition(self.right_button1,'[!String.IsEmpty(Container(6).ListItem(Container(6).Position).Label) + !String.IsEmpty(Container(6).ListItem(Container(6).Position).Path)]')
|
|
self.setEnableCondition(self.right_button2,'[!String.IsEmpty(Container(6).ListItem(Container(6).Position).Property(chnum))]')
|
|
else:
|
|
self.setLabels(self.right_button1,LANGUAGE(32062))#Close
|
|
self.setLabels(self.right_button2,LANGUAGE(32060))#Cancel
|
|
self.setEnableCondition(self.right_button1,'[!String.IsEmpty(Container(6).ListItem(Container(6).Position).Property(chnum))]')
|
|
self.setEnableCondition(self.right_button2,'[!String.IsEmpty(Container(6).ListItem(Container(6).Position).Path)]')
|
|
|
|
self.setLabels(self.right_button3,LANGUAGE(32235))#Preview
|
|
self.setLabels(self.right_button4,LANGUAGE(32239))#Clear
|
|
self.setEnableCondition(self.right_button3,'[!String.IsEmpty(Container(6).ListItem(Container(6).Position).Path) + String.IsEqual(Container(6).ListItem(Container(6).Position).Property(radio),False)]')
|
|
self.setEnableCondition(self.right_button4,'[!String.IsEmpty(Container(6).ListItem(Container(6).Position).Path)]')
|
|
|
|
|
|
def setFocusPOS(self, listitems, chnum=None, ignore=True):
|
|
for idx, listitem in enumerate(listitems):
|
|
chnumber = int(cleanLabel(listitem.getLabel()).strip('|'))
|
|
if ignore and chnumber > CHANNEL_LIMIT: continue
|
|
elif chnum is not None and chnum == chnumber: return idx
|
|
elif chnum is None and cleanLabel(listitem.getLabel2()): return idx
|
|
return 0
|
|
|
|
|
|
def getRuleAbbr(self, citem, myId, optionindex):
|
|
value = citem.get('rules',{}).get(str(myId),{}).get('values',{}).get(str(optionindex))
|
|
self.log('getRuleAbbr, id = %s, myId = %s, optionindex = %s, optionvalue = %s'%(citem.get('id',-1),myId,optionindex,value))
|
|
return value
|
|
|
|
|
|
def getLogoColor(self, citem):
|
|
self.log('getLogoColor, id = %s'%(citem.get('id',-1)))
|
|
if (citem.get('logo') and citem.get('name')) is None: return 'FFFFFFFF'
|
|
elif citem.get('rules',{}).get("1"):
|
|
if (self.getRuleAbbr(citem,1,4) or self.resource.isMono(citem['logo'])):
|
|
return self.getRuleAbbr(citem,1,3)
|
|
return SETTINGS.getSetting('ChannelBug_Color')
|
|
|
|
|
|
def buildChannelItem(self, citem: dict={}, focuskey: str='path'):
|
|
self.log('buildChannelItem, id = %s, focuskey = %s'%(citem.get('id'),focuskey))
|
|
def __buildItem(key):
|
|
key = key.lower()
|
|
value = citem.get(key,' ')
|
|
if key in ["number","type","logo","id","catchup"]: return # keys to ignore, internal use only.
|
|
elif isinstance(value,(list,dict)):
|
|
if key == "group" : value = ('|'.join(sorted(set(value))) or LANGUAGE(30127))
|
|
elif key == "path" : value = '|'.join(value)
|
|
elif key == "rules" : value = '|'.join([rule.name for key, rule in list(self.rule.loadRules([citem]).get(citem['id'],{}).items())])#todo load rule names
|
|
elif not isinstance(value,str): value = str(value)
|
|
elif not value: value = ' '
|
|
return self.buildListItem(LABEL.get(key,' '),value,items={'key':key,'value':value,'citem':citem,'chname':citem["name"],'chnum':'%i'%(citem["number"]),'radio':citem.get('radio',False),'description':DESC.get(key,''),'colorDiffuse':self.getLogoColor(citem)})
|
|
|
|
self.togglechanList(False)
|
|
with self.toggleSpinner():
|
|
LABEL = {'name' : LANGUAGE(32092),
|
|
'path' : LANGUAGE(32093),
|
|
'group' : LANGUAGE(32094),
|
|
'rules' : LANGUAGE(32095),
|
|
'radio' : LANGUAGE(32091),
|
|
'favorite': LANGUAGE(32090)}
|
|
|
|
DESC = {'name' : LANGUAGE(32085),
|
|
'path' : LANGUAGE(32086),
|
|
'group' : LANGUAGE(32087),
|
|
'rules' : LANGUAGE(32088),
|
|
'radio' : LANGUAGE(32084),
|
|
'favorite': LANGUAGE(32083)}
|
|
|
|
listitems = poolit(__buildItem)(list(self.newChannel.keys()))
|
|
self.itemList.addItems(listitems)
|
|
self.itemList.selectItem([idx for idx, liz in enumerate(listitems) if liz.getProperty('key')== focuskey][0])
|
|
self.setFocus(self.itemList)
|
|
|
|
|
|
def itemInput(self, channelListItem=xbmcgui.ListItem()):
|
|
def __getName(citem: dict={}, name: str=''):
|
|
return DIALOG.inputDialog(message=LANGUAGE(32079),default=name), citem
|
|
|
|
def __getPath(citem: dict={}, paths: list=[]):
|
|
return self.getPaths(citem, paths)
|
|
|
|
def __getGroups(citem: dict={}, groups: list=[]):
|
|
groups = list([_f for _f in groups if _f])
|
|
ngroups = sorted([_f for _f in set(SETTINGS.getSetting('User_Groups').split('|') + GROUP_TYPES + groups) if _f])
|
|
ngroups.insert(0, '-%s'%(LANGUAGE(30064)))
|
|
selects = DIALOG.selectDialog(ngroups,header=LANGUAGE(32081),preselect=findItemsInLST(ngroups,groups),useDetails=False)
|
|
if 0 in selects:
|
|
SETTINGS.setSetting('User_Groups',DIALOG.inputDialog(LANGUAGE(32044), default=SETTINGS.getSetting('User_Groups')))
|
|
return __getGroups(citem, groups)
|
|
elif len(ngroups) > 0: groups = [ngroups[idx] for idx in selects]
|
|
if not groups: groups = [LANGUAGE(30127)]
|
|
return groups, citem
|
|
|
|
def __getRule(citem: dict={}, rules: dict={}):
|
|
return self.getRules(citem, rules)
|
|
|
|
def __getBool(citem: dict={}, state: bool=False):
|
|
return not bool(state), citem
|
|
|
|
key = channelListItem.getProperty('key')
|
|
value = channelListItem.getProperty('value')
|
|
citem = loadJSON(channelListItem.getProperty('citem'))
|
|
self.log('itemInput, In value = %s, key = %s\ncitem = %s'%(value,key,citem))
|
|
|
|
KEY_INPUT = {"name" : {'func':__getName , 'kwargs':{'citem':citem, 'name' :citem.get('name','')}},
|
|
"path" : {'func':__getPath , 'kwargs':{'citem':citem, 'paths' :citem.get('path',[])}},
|
|
"group" : {'func':__getGroups, 'kwargs':{'citem':citem, 'groups':citem.get('group',[])}},
|
|
"rules" : {'func':__getRule , 'kwargs':{'citem':citem, 'rules' :self.rule.loadRules([citem],incRez=False).get(citem['id'],{})}},
|
|
"radio" : {'func':__getBool , 'kwargs':{'citem':citem, 'state' :citem.get('radio',False)}},
|
|
"favorite" : {'func':__getBool , 'kwargs':{'citem':citem, 'state' :citem.get('favorite',False)}}}
|
|
|
|
action = KEY_INPUT.get(key)
|
|
retval, citem = action['func'](*action.get('args',()),**action.get('kwargs',{}))
|
|
retval, citem = self.validateInputs(key,retval,citem)
|
|
if not retval is None:
|
|
self.madeItemchange = True
|
|
if key in list(self.newChannel.keys()): citem[key] = retval
|
|
self.log('itemInput, Out value = %s, key = %s\ncitem = %s'%(retval,key,citem))
|
|
return citem
|
|
|
|
|
|
def getPaths(self, citem: dict={}, paths: list=[]):
|
|
select = -1
|
|
epaths = paths.copy()
|
|
pathLST = list([_f for _f in paths if _f])
|
|
lastOPT = None
|
|
|
|
if not citem.get('radio',False) and isRadio({'path':paths}): citem['radio'] = True #set radio on music paths
|
|
if citem.get('radio',False): excLST = [10,12,21,22]
|
|
else: excLST = [11,13,21]
|
|
|
|
while not self.monitor.abortRequested() and not select is None:
|
|
with self.toggleSpinner():
|
|
npath = None
|
|
lizLST = [self.buildListItem('%s|'%(idx+1),path,paths=[path],icon=DUMMY_ICON.format(text=str(idx+1)),items={'citem':citem,'idx':idx+1}) for idx, path in enumerate(pathLST) if path]
|
|
lizLST.insert(0,self.buildListItem('[COLOR=white][B]%s[/B][/COLOR]'%(LANGUAGE(32100)),LANGUAGE(33113),icon=ICON,items={'key':'add','citem':citem,'idx':0}))
|
|
if len(pathLST) > 0 and epaths != pathLST: lizLST.insert(1,self.buildListItem('[COLOR=white][B]%s[/B][/COLOR]'%(LANGUAGE(32101)),LANGUAGE(33114),icon=ICON,items={'key':'save','citem':citem}))
|
|
|
|
select = DIALOG.selectDialog(lizLST, header=LANGUAGE(32086), preselect=lastOPT, multi=False)
|
|
with self.toggleSpinner():
|
|
if not select is None:
|
|
key, path = lizLST[select].getProperty('key'), lizLST[select].getPath()
|
|
try: lastOPT = int(lizLST[select].getProperty('idx'))
|
|
except: lastOPT = None
|
|
if key == 'add':
|
|
retval = DIALOG.browseSources(heading=LANGUAGE(32080), exclude=excLST, monitor=True)
|
|
if not retval is None:
|
|
npath, citem = self.validatePaths(retval,citem)
|
|
if npath: pathLST.append(npath)
|
|
elif key == 'save':
|
|
paths = pathLST
|
|
break
|
|
elif path in pathLST:
|
|
retval = DIALOG.yesnoDialog(LANGUAGE(32102), customlabel=LANGUAGE(32103))
|
|
if retval in [1,2]: pathLST.pop(pathLST.index(path))
|
|
if retval == 2:
|
|
with self.toggleSpinner():
|
|
npath, citem = self.validatePaths(DIALOG.browseSources(heading=LANGUAGE(32080), default=path, monitor=True, exclude=excLST), citem)
|
|
pathLST.append(npath)
|
|
self.log('getPaths, paths = %s'%(paths))
|
|
return paths, citem
|
|
|
|
|
|
def getRules(self, citem: dict={}, rules: dict={}):
|
|
if citem.get('id') is None or len(citem.get('path',[])) == 0: DIALOG.notificationDialog(LANGUAGE(32071))
|
|
else:
|
|
select = -1
|
|
erules = rules.copy()
|
|
ruleLST = rules.copy()
|
|
lastIDX = None
|
|
lastXID = None
|
|
while not self.monitor.abortRequested() and not select is None:
|
|
with self.toggleSpinner():
|
|
nrule = None
|
|
crules = self.rule.loadRules([citem],append=True,incRez=False).get(citem['id'],{}) #all rule instances w/ channel rules
|
|
arules = [rule for key, rule in list(crules.items()) if not ruleLST.get(key)] #all unused rule instances
|
|
lizLST = [self.buildListItem(rule.name,rule.getTitle(),icon=DUMMY_ICON.format(text=str(rule.myId)),items={'myId':rule.myId,'citem':citem,'idx':list(ruleLST.keys()).index(key)+1}) for key, rule in list(ruleLST.items()) if rule.myId]
|
|
lizLST.insert(0,self.buildListItem('[COLOR=white][B]%s[/B][/COLOR]'%(LANGUAGE(32173)),"",icon=ICON,items={'key':'add' ,'citem':citem,'idx':0}))
|
|
if len(ruleLST) > 0 and erules != ruleLST: lizLST.insert(1,self.buildListItem('[COLOR=white][B]%s[/B][/COLOR]'%(LANGUAGE(32174)),"",icon=ICON,items={'key':'save','citem':citem}))
|
|
|
|
select = DIALOG.selectDialog(lizLST, header=LANGUAGE(32095), preselect=lastIDX, multi=False)
|
|
if not select is None:
|
|
key, myId = lizLST[select].getProperty('key'), int(lizLST[select].getProperty('myId') or '-1')
|
|
try: lastIDX = int(lizLST[select].getProperty('idx'))
|
|
except: lastIDX = None
|
|
if key == 'add':
|
|
with self.toggleSpinner():
|
|
lizLST = [self.buildListItem(rule.name,rule.description,icon=DUMMY_ICON.format(text=str(rule.myId)),items={'idx':idx,'myId':rule.myId,'citem':citem}) for idx, rule in enumerate(arules) if rule.myId]
|
|
select = DIALOG.selectDialog(lizLST, header=LANGUAGE(32072), preselect=lastXID, multi=False)
|
|
try: lastXID = int(lizLST[select].getProperty('idx'))
|
|
except: lastXID = -1
|
|
nrule, citem = self.getRule(citem, arules[lastXID])
|
|
if not nrule is None: ruleLST.update({str(nrule.myId):nrule})
|
|
elif key == 'save':
|
|
rules = ruleLST
|
|
break
|
|
elif ruleLST.get(str(myId)):
|
|
retval = DIALOG.yesnoDialog(LANGUAGE(32175), customlabel=LANGUAGE(32176))
|
|
if retval in [1,2]: ruleLST.pop(str(myId))
|
|
if retval == 2:
|
|
nrule, citem = self.getRule(citem, crules.get(str(myId),{}))
|
|
if not nrule is None: ruleLST.update({str(nrule.myId):nrule})
|
|
# elif not ruleLST.get(str(myId)):
|
|
# nrule, citem = self.getRule(citem, crules.get(str(myId),{}))
|
|
# if not nrule is None: ruleLST.update({str(nrule.myId):nrule})
|
|
self.log('getRules, rules = %s'%(len(rules)))
|
|
return self.rule.dumpRules(rules), citem
|
|
|
|
|
|
def getRule(self, citem={}, rule={}):
|
|
self.log('getRule, name = %s'%(rule.name))
|
|
if rule.exclude and True in list(set([True for p in citem.get('path',[]) if p.endswith('.xsp')])): return DIALOG.notificationDialog(LANGUAGE(32178))
|
|
else:
|
|
select = -1
|
|
while not self.monitor.abortRequested() and not select is None:
|
|
with self.toggleSpinner():
|
|
lizLST = [self.buildListItem('%s = %s'%(rule.optionLabels[idx],rule.optionValues[idx]),rule.optionDescriptions[idx],DUMMY_ICON.format(text=str(idx+1)),[str(rule.myId)],{'value':optionValue,'idx':idx,'myId':rule.myId,'citem':citem}) for idx, optionValue in enumerate(rule.optionValues)]
|
|
select = DIALOG.selectDialog(lizLST, header='%s %s - %s'%(LANGUAGE(32176),rule.myId,rule.name), multi=False)
|
|
if not select is None:
|
|
try: rule.onAction(int(lizLST[select].getProperty('idx') or "0"))
|
|
except Exception as e:
|
|
self.log("getRule, onAction failed! %s"%(e), xbmc.LOGERROR)
|
|
DIALOG.okDialog(LANGUAGE(32000))
|
|
return rule, citem
|
|
|
|
|
|
def setID(self, citem: dict={}) -> dict:
|
|
if not citem.get('id') and citem.get('name') and citem.get('path') and citem.get('number'):
|
|
citem['id'] = getChannelID(citem['name'], citem['path'], citem['number'])
|
|
self.log('setID, id = %s'%(citem['id']))
|
|
return citem
|
|
|
|
|
|
def setName(self, path, citem: dict={}) -> dict:
|
|
with self.toggleSpinner():
|
|
if citem.get('name'): return citem
|
|
elif path.strip('/').endswith(('.xml','.xsp')): citem['name'] = XSP().getName(path)
|
|
elif path.startswith(tuple(DB_TYPES+WEB_TYPES+VFS_TYPES)): citem['name'] = self.getMontiorList().getLabel()
|
|
else: citem['name'] = os.path.basename(os.path.dirname(path)).strip('/')
|
|
self.log('setName, id = %s, name = %s'%(citem['id'],citem['name']))
|
|
return citem
|
|
|
|
|
|
def setLogo(self, name=None, citem={}, force=False):
|
|
name = (name or citem.get('name'))
|
|
if name:
|
|
if force: logo = ''
|
|
else: logo = citem.get('logo')
|
|
if not logo or logo in [LOGO,COLOR_LOGO,ICON]:
|
|
with self.toggleSpinner():
|
|
citem['logo'] = self.resource.getLogo(citem,auto=True)
|
|
self.log('setLogo, id = %s, logo = %s, force = %s'%(citem.get('id'),citem.get('logo'),force))
|
|
return citem
|
|
|
|
|
|
def validateInputs(self, key, value, citem):
|
|
self.log('validateInputs, key = %s, value = %s'%(key,value))
|
|
def __validateName(name, citem):
|
|
if name and (len(name) > 1 or len(name) < 128):
|
|
citem['name'] = validString(name)
|
|
self.log('__validateName, name = %s'%(citem['name']))
|
|
return citem['name'], self.setLogo(name, citem, force=True)
|
|
return None, citem
|
|
|
|
def __validatePath(paths, citem):
|
|
if len(paths) > 0:
|
|
name, citem = __validateName(citem.get('name',''),self.setName(paths[0], citem))
|
|
self.log('__validatePath, name = %s, paths = %s'%(name,paths))
|
|
return paths, citem
|
|
return None, citem
|
|
|
|
def __validateGroup(groups, citem):
|
|
return groups, citem #todo check values
|
|
|
|
def __validateRules(rules, citem):
|
|
return rules, citem #todo check values
|
|
|
|
def __validateBool(state, citem):
|
|
if isinstance(state,bool): return state, citem
|
|
return None, citem
|
|
|
|
KEY_VALIDATION = {'name' :__validateName,
|
|
'path' :__validatePath,
|
|
'group' :__validateGroup,
|
|
'rules' :__validateRules,
|
|
'radio' :__validateBool,
|
|
'favorite':__validateBool}.get(key,None)
|
|
try:
|
|
with toggleSpinner():
|
|
retval, citem = KEY_VALIDATION(value,citem)
|
|
if retval is None:
|
|
DIALOG.notificationDialog(LANGUAGE(32077)%key.title())
|
|
return None , citem
|
|
return retval, self.setID(citem)
|
|
except Exception as e:
|
|
self.log("validateInputs, key = %s no action! %s"%(key,e))
|
|
return value, citem
|
|
|
|
|
|
def validatePaths(self, path, citem, spinner=True):
|
|
self.log('validatePaths, path = %s'%path)
|
|
def __set(path, citem):
|
|
citem = self.setName(path, citem)
|
|
return path, self.setLogo(citem.get('name'),citem)
|
|
|
|
def __seek(item, citem, cnt, dia, passed=False):
|
|
player = PLAYER()
|
|
if player.isPlaying(): return DIALOG.notificationDialog(LANGUAGE(30136))
|
|
# todo test seek for support disable via adv. rule if fails.
|
|
# todo set seeklock rule if seek == False
|
|
liz = xbmcgui.ListItem('Seek Test', path=item.get('file'))
|
|
liz.setProperty('startoffset', str(int(item.get('duration')//8)))
|
|
infoTag = ListItemInfoTag(liz, 'video')
|
|
infoTag.set_resume_point({'ResumeTime':int(item.get('duration')/4),'TotalTime':int(item.get('duration')*60)})
|
|
|
|
getTime = 0
|
|
waitTime = FIFTEEN
|
|
player.play(item.get('file'),liz)
|
|
while not self.monitor.abortRequested():
|
|
waitTime -= 1
|
|
self.log('validatePaths _seek, waiting (%s) to seek %s'%(waitTime, item.get('file')))
|
|
if self.monitor.waitForAbort(1.0) or waitTime < 1: break
|
|
elif not player.isPlaying(): continue
|
|
elif ((int(player.getTime()) > getTime) or BUILTIN.getInfoBool('SeekEnabled','Player')):
|
|
self.log('validatePaths _seek, found playable and seek-able file %s'%(item.get('file')))
|
|
passed = True
|
|
break
|
|
|
|
player.stop()
|
|
del player
|
|
|
|
if not passed:
|
|
retval = DIALOG.yesnoDialog(LANGUAGE(30202),customlabel='Try Again (%s)'%(cnt))
|
|
if retval == 1: passed = True
|
|
elif retval == 2: passed = None
|
|
self.log('validatePaths _seek, passed = %s'%(passed))
|
|
return passed
|
|
|
|
|
|
def __vfs(path, citem, valid=False):
|
|
if isRadio({'path':[path]}) or isMixed_XSP({'path':[path]}): return True #todo check mixed xsp.
|
|
with BUILTIN.busy_dialog():
|
|
items = self.jsonRPC.walkFileDirectory(path, 'music' if isRadio({'path':[path]}) else 'video', depth=5, retItem=True)
|
|
|
|
if len(items) > 0:
|
|
cnt = 3
|
|
msg = '%s %s, %s..\n%s'%(LANGUAGE(32098),'Path',LANGUAGE(32099),'%s...'%(str(path)))
|
|
dia = DIALOG.progressDialog(message=msg)
|
|
for idx, dir in enumerate(items):
|
|
if self.monitor.waitForAbort(0.0001): break
|
|
elif cnt <= 3 and cnt > 0:
|
|
item = random.choice(items.get(dir,[]))
|
|
msg = '%s %s...\n%s\n%s'%(LANGUAGE(32098),'Duration','%s...'%(dir),'%s...'%(item.get('file','')))
|
|
dia = DIALOG.progressDialog(int((idx*100)//len(items)), control=dia, message=msg)
|
|
item.update({'duration':self.jsonRPC.getDuration(item.get('file'), item, accurate=bool(SETTINGS.getSettingInt('Duration_Type')))})
|
|
if item.get('duration',0) == 0: continue
|
|
msg = '%s %s...\n%s\n%s'%(LANGUAGE(32098),'Seeking','%s...'%(str(dir)),'%s...'%(str(item.get('file',''))))
|
|
dia = DIALOG.progressDialog(int((idx*100)//len(items)), control=dia, message=msg)
|
|
valid = __seek(item, citem, cnt, dia)
|
|
if valid is None: cnt -=1
|
|
else: break
|
|
DIALOG.progressDialog(100,control=dia)
|
|
return valid
|
|
|
|
with self.toggleSpinner(allow=spinner):
|
|
if __vfs(path, citem): return __set(path, citem)
|
|
|
|
DIALOG.notificationDialog(LANGUAGE(32030))
|
|
return None, citem
|
|
|
|
|
|
def openEditor(self, path):
|
|
self.log('openEditor, path = %s'%(path))
|
|
if '|' in path:
|
|
path = path.split('|')
|
|
path = path[0]#prompt user to select:
|
|
media = 'video' if 'video' in path else 'music'
|
|
if '.xsp' in path: return self.openEditor(path,media)
|
|
elif '.xml' in path: return self.openNode(path,media)
|
|
|
|
|
|
def previewChannel(self, citem, retCntrl=None):
|
|
def __buildItem(fileList, fitem):
|
|
return self.buildListItem('%s| %s'%(fileList.index(fitem),fitem.get('showlabel',fitem.get('label'))), fitem.get('file') ,icon=(getThumb(fitem,opt=EPG_ARTWORK) or {0:FANART,1:COLOR_LOGO}[EPG_ARTWORK]))
|
|
|
|
def __fileList(citem):
|
|
from builder import Builder
|
|
builder = Builder()
|
|
fileList = []
|
|
start_time = 0
|
|
end_time = 0
|
|
if PROPERTIES.isInterruptActivity(): PROPERTIES.setInterruptActivity(False)
|
|
while not self.monitor.abortRequested() and PROPERTIES.isRunning('OVERLAY_MANAGER'):
|
|
if self.monitor.waitForAbort(1.0): break
|
|
elif not PROPERTIES.isRunning('builder.build') and not PROPERTIES.isInterruptActivity():
|
|
DIALOG.notificationDialog('%s: [B]%s[/B]\n%s'%(LANGUAGE(32236),citem.get('name','Untitled'),LANGUAGE(32140)))
|
|
tmpcitem = citem.copy()
|
|
tmpcitem['id'] = getChannelID(citem['name'], citem['path'], random.random())
|
|
start_time = time.time()
|
|
fileList = builder.build([tmpcitem],preview=True)
|
|
end_time = time.time()
|
|
if not fileList or isinstance(fileList,list): break
|
|
del builder
|
|
if not PROPERTIES.isInterruptActivity(): PROPERTIES.setInterruptActivity(True)
|
|
return fileList, round(abs(end_time-start_time),2)
|
|
|
|
if not PROPERTIES.isRunning('previewChannel'):
|
|
with PROPERTIES.chkRunning('previewChannel'), self.toggleSpinner():
|
|
listitems = []
|
|
fileList, run_time = __fileList(citem)
|
|
if not isinstance(fileList,list) and not fileList: DIALOG.notificationDialog('%s or\n%s'%(LANGUAGE(32030),LANGUAGE(32000)))
|
|
else:
|
|
listitems = poolit(__buildItem)(*(fileList,fileList))
|
|
self.log('previewChannel, id = %s, listitems = %s'%(citem['id'],len(listitems)))
|
|
if len(listitems) > 0: return DIALOG.selectDialog(listitems, header='%s: [B]%s[/B] - Build Time: [B]%ss[/B]'%(LANGUAGE(32235),citem.get('name','Untitled'),f"{run_time:.2f}"))
|
|
if retCntrl: self.setFocusId(retCntrl)
|
|
|
|
|
|
def getMontiorList(self):
|
|
self.log('getMontiorList')
|
|
try:
|
|
with self.toggleSpinner():
|
|
labels = sorted(set([cleanLabel(value).title() for info in DIALOG.getInfoMonitor() for key, value in list(info.items()) if value not in ['','..'] and key not in ['path','logo']]))
|
|
itemLST = [self.buildListItem(label,icon=ICON) for label in labels]
|
|
if len(itemLST) == 0: raise Exception()
|
|
itemSEL = DIALOG.selectDialog(itemLST,LANGUAGE(32078)%('Name'),useDetails=True,multi=False)
|
|
if itemSEL is not None: return itemLST[itemSEL]
|
|
else: raise Exception()
|
|
except: return xbmcgui.ListItem(LANGUAGE(32079))
|
|
|
|
|
|
def clearChannel(self, item, prompt=True, open=False):
|
|
self.log('clearChannel, channelPOS = %s'%(item['number'] - 1))
|
|
with self.toggleSpinner():
|
|
if item['number'] > CHANNEL_LIMIT: return DIALOG.notificationDialog(LANGUAGE(32238))
|
|
elif prompt and not DIALOG.yesnoDialog(LANGUAGE(32073)): return item
|
|
self.madeItemchange = True
|
|
nitem = self.newChannel.copy()
|
|
nitem['number'] = item['number'] #preserve channel number
|
|
self.saveChannelItems(nitem, open)
|
|
|
|
|
|
def moveChannel(self, citem, channelPOS):
|
|
self.log('moveChannel, channelPOS = %s'%(channelPOS))
|
|
if citem['number'] > CHANNEL_LIMIT: return DIALOG.notificationDialog(LANGUAGE(32064))
|
|
retval = DIALOG.inputDialog(LANGUAGE(32137), key=xbmcgui.INPUT_NUMERIC, opt=citem['number'])
|
|
if retval:
|
|
retval = int(retval)
|
|
if (retval > 0 and retval < CHANNEL_LIMIT) and retval != channelPOS + 1:
|
|
if DIALOG.yesnoDialog('%s %s %s from [B]%s[/B] to [B]%s[/B]?'%(LANGUAGE(32136),citem['name'],LANGUAGE(32023),citem['number'],retval)):
|
|
with self.toggleSpinner():
|
|
if retval in [channel.get('number') for channel in self.newChannels if channel.get('path')]: DIALOG.notificationDialog(LANGUAGE(32138))
|
|
else:
|
|
self.madeItemchange = True
|
|
nitem = self.newChannel.copy()
|
|
nitem['number'] = channelPOS + 1
|
|
self.newChannels[channelPOS] = nitem
|
|
citem['number'] = retval
|
|
self.saveChannelItems(citem)
|
|
|
|
|
|
def switchLogo(self, channelData, channelPOS):
|
|
def __cleanLogo(chlogo):
|
|
#todo convert resource from vfs to fs
|
|
# return chlogo.replace('resource://','special://home/addons/')
|
|
# resource = path.replace('/resources','').replace(,)
|
|
# resource://resource.images.studios.white/Amazon.png
|
|
return chlogo
|
|
|
|
def __select():
|
|
def _build(logos, logo):
|
|
label = os.path.splitext(os.path.basename(logo))[0]
|
|
return self.buildListItem('%s| %s'%(logos.index(logo)+1, label.upper() if len(label) <= 4 else label.title()), unquoteString(logo), logo, [logo])
|
|
|
|
DIALOG.notificationDialog(LANGUAGE(32140))
|
|
with self.toggleSpinner():
|
|
chname = channelData.get('name')
|
|
logos = self.resource.selectLogo(channelData)
|
|
listitems = poolit(_build)(*(logos,logos))
|
|
select = DIALOG.selectDialog(listitems,'%s (%s)'%(LANGUAGE(32066).split('[CR]')[1],chname),useDetails=True,multi=False)
|
|
if select is not None: return listitems[select].getPath()
|
|
|
|
def __browse():
|
|
with self.toggleSpinner():
|
|
chname = channelData.get('name')
|
|
retval = DIALOG.browseSources(type=1,heading='%s (%s)'%(LANGUAGE(32066).split('[CR]')[0],chname), default=channelData.get('icon',''), shares='files', mask=xbmc.getSupportedMedia('picture'), exclude=[12,13,14,15,16,17,21,22])
|
|
if FileAccess.copy(__cleanLogo(retval), os.path.join(LOGO_LOC,'%s%s'%(chname,retval[-4:])).replace('\\','/')):
|
|
if FileAccess.exists(os.path.join(LOGO_LOC,'%s%s'%(chname,retval[-4:])).replace('\\','/')):
|
|
return os.path.join(LOGO_LOC,'%s%s'%(chname,retval[-4:])).replace('\\','/')
|
|
return retval
|
|
|
|
def __match():
|
|
with self.toggleSpinner():
|
|
return self.resource.getLogo(channelData,auto=True)
|
|
|
|
if not channelData.get('name'): return DIALOG.notificationDialog(LANGUAGE(32065))
|
|
chlogo = None
|
|
retval = DIALOG.yesnoDialog(LANGUAGE(32066), heading ='%s - %s'%(ADDON_NAME,LANGUAGE(32172)),
|
|
nolabel = LANGUAGE(32067), #Select
|
|
yeslabel = LANGUAGE(32068), #Browse
|
|
customlabel = LANGUAGE(30022)) #Auto
|
|
|
|
if retval == 0: chlogo = __select()
|
|
elif retval == 1: chlogo = __browse()
|
|
elif retval == 2: chlogo = __match()
|
|
else: DIALOG.notificationDialog(LANGUAGE(32070))
|
|
if chlogo and chlogo != LOGO:
|
|
self.log('switchLogo, chname = %s, chlogo = %s'%(channelData.get('name'),chlogo))
|
|
DIALOG.notificationDialog(LANGUAGE(32139))
|
|
self.madeChanges = True
|
|
channelData['logo'] = chlogo
|
|
self.newChannels[channelPOS] = channelData
|
|
self.fillChanList(self.newChannels,refresh=True,focus=channelPOS)
|
|
|
|
|
|
def isVisible(self, cntrl):
|
|
try:
|
|
if isinstance(cntrl, int): cntrl = self.getControl(cntrl)
|
|
state = cntrl.isVisible()
|
|
except: state = self.cntrlStates.get(cntrl.getId(),False)
|
|
self.log('isVisible, cntrl = %s, state = %s'%(cntrl.getId(),state))
|
|
return state
|
|
|
|
|
|
def setVisibility(self, cntrl, state):
|
|
try:
|
|
if isinstance(cntrl, int): cntrl = self.getControl(cntrl)
|
|
cntrl.setVisible(state)
|
|
self.cntrlStates[cntrl.getId()] = state
|
|
self.log('setVisibility, cntrl = ' + str(cntrl.getId()) + ', state = ' + str(state))
|
|
except Exception as e: self.log("setVisibility, failed! %s"%(e), xbmc.LOGERROR)
|
|
|
|
|
|
def getLabels(self, cntrl):
|
|
try:
|
|
if isinstance(cntrl, int): cntrl = self.getControl(cntrl)
|
|
return cntrl.getLabel(), cntrl.getLabel2()
|
|
except Exception as e: return '',''
|
|
|
|
|
|
def setImages(self, cntrl, image='NA.png'):
|
|
try:
|
|
if isinstance(cntrl, int): cntrl = self.getControl(cntrl)
|
|
cntrl.setImage(image)
|
|
except Exception as e: self.log("setImages, failed! %s"%(e), xbmc.LOGERROR)
|
|
|
|
|
|
def setLabels(self, cntrl, label='', label2=''):
|
|
try:
|
|
if isinstance(cntrl, int): cntrl = self.getControl(cntrl)
|
|
cntrl.setLabel(str(label), str(label2))
|
|
self.setVisibility(cntrl,(len(label) > 0 or len(label2) > 0))
|
|
except Exception as e: self.log("setLabels, failed! %s"%(e), xbmc.LOGERROR)
|
|
|
|
|
|
def setEnableCondition(self, cntrl, condition):
|
|
try:
|
|
if isinstance(cntrl, int): cntrl = self.getControl(cntrl)
|
|
cntrl.setEnableCondition(condition)
|
|
except Exception as e: self.log("setEnableCondition, failed! %s"%(e), xbmc.LOGERROR)
|
|
|
|
|
|
def resetPagination(self, citem):
|
|
if isinstance(citem, list): [self.resetPagination(item) for item in citem]
|
|
else:
|
|
with self.toggleSpinner():
|
|
self.log('resetPagination, citem = %s'%(citem))
|
|
[self.jsonRPC.resetPagination(citem.get('id'), path) for path in citem.get('path',[]) if citem.get('id')]
|
|
|
|
|
|
def saveChannelItems(self, citem: dict={}, open=False):
|
|
self.log('saveChannelItems [%s], open = %s'%(citem.get('id'),open))
|
|
if self.madeItemchange:
|
|
self.madeChanges = True
|
|
self.newChannels[citem['number'] - 1] = citem
|
|
self.fillChanList(self.newChannels,True,(citem['number'] - 1),citem if open else None)
|
|
self.madeItemchange = False
|
|
return citem
|
|
|
|
|
|
def closeChannel(self, citem, focus=0, open=False):
|
|
self.log('closeChannel')
|
|
if self.madeItemchange:
|
|
if DIALOG.yesnoDialog(LANGUAGE(32243)): return self.saveChannelItems(citem, open)
|
|
self.togglechanList(focus=focus)
|
|
|
|
|
|
def saveChanges(self):
|
|
self.log("saveChanges")
|
|
def __validateChannels(channelList):
|
|
def _validate(citem):
|
|
if citem.get('name') and citem.get('path'):
|
|
if citem['number'] <= CHANNEL_LIMIT: citem['type'] = "Custom"
|
|
return self.setID(citem)
|
|
channelList = setDictLST(self.channels.sortChannels([_f for _f in [_validate(channel) for channel in channelList] if _f]))
|
|
self.log('__validateChannels, channelList = %s'%(len(channelList)))
|
|
return channelList
|
|
|
|
if self.madeChanges:
|
|
if DIALOG.yesnoDialog(LANGUAGE(32076)):
|
|
with self.toggleSpinner():
|
|
channels = __validateChannels(self.newChannels)
|
|
changes = diffLSTDICT(__validateChannels(self.channelList),channels)
|
|
added = [citem.get('id') for citem in changes.get('added',[])]
|
|
removed = [citem.get('id') for citem in changes.get('removed',[])]
|
|
ids = added+removed
|
|
citems = changes.get('added',[])+changes.get('removed',[])
|
|
self.log("saveChanges, channels = %s, added = %s, removed = %s"%(len(channels), len(added), len(removed)))
|
|
if self.server:
|
|
payload = {'uuid':SETTINGS.getMYUUID(),'name':self.friendly,'payload':channels}
|
|
requestURL('http://%s/%s'%(self.server.get('host'),CHANNELFLE), data=payload, header=HEADER, json_data=True)
|
|
else:
|
|
self.channels.setChannels(channels) #save changes
|
|
self.resetPagination(citems) #clear auto pagination cache
|
|
SETTINGS.setResetChannels(ids) #clear guidedata
|
|
PROPERTIES.setUpdateChannels(ids) #update channel meta.
|
|
self.madeChanges = False
|
|
self.closeManager()
|
|
|
|
|
|
def closeManager(self):
|
|
self.log('closeManager')
|
|
if self.madeChanges: self.saveChanges()
|
|
else: self.close()
|
|
|
|
|
|
def __exit__(self):
|
|
self.log('__exit__')
|
|
del self.resource
|
|
del self.jsonRPC
|
|
del self.rule
|
|
del self.channels
|
|
|
|
|
|
def getFocusItems(self, controlId=None):
|
|
focusItems = dict()
|
|
if controlId in [5,6,7,9001,9002,9003,9004]:
|
|
label, label2 = self.getLabels(controlId)
|
|
try: snum = int(cleanLabel(label.replace("|",'')))
|
|
except: snum = 1
|
|
if self.isVisible(self.chanList):
|
|
cntrl = controlId
|
|
sitem = self.chanList.getSelectedItem()
|
|
citem = loadJSON(sitem.getProperty('citem'))
|
|
chnum = (citem.get('number') or snum)
|
|
chpos = self.chanList.getSelectedPosition()
|
|
itpos = -1
|
|
elif self.isVisible(self.itemList):
|
|
cntrl = controlId
|
|
sitem = self.itemList.getSelectedItem()
|
|
citem = loadJSON(sitem.getProperty('citem'))
|
|
chnum = (citem.get('number') or snum)
|
|
chpos = chnum - 1
|
|
itpos = self.itemList.getSelectedPosition()
|
|
else:
|
|
sitem = xbmcgui.ListItem()
|
|
cntrl = (self.focusItems.get('cntrl') or controlId)
|
|
citem = (self.focusItems.get('citem') or {})
|
|
chnum = (self.focusItems.get('number') or snum)
|
|
chpos = (self.focusItems.get('chpos') or chnum - 1)
|
|
itpos = (self.focusItems.get('itpos') or -1)
|
|
self.focusItems.update({'retCntrl':cntrl,'label':label,'label2':label2,'number':chnum,'chpos':chpos,'itpos':itpos,'item':sitem,'citem':citem})
|
|
self.log('getFocusItems, controlId = %s, focusItems = %s'%(controlId,self.focusItems))
|
|
return self.focusItems
|
|
|
|
|
|
def onAction(self, act):
|
|
actionId = act.getId()
|
|
if (time.time() - self.lastActionTime) < .5 and actionId not in ACTION_PREVIOUS_MENU: action = ACTION_INVALID # during certain times we just want to discard all input
|
|
else:
|
|
if actionId in ACTION_PREVIOUS_MENU:
|
|
self.log('onAction: actionId = %s'%(actionId))
|
|
if xbmcgui.getCurrentWindowDialogId() == "13001": BUILTIN.executebuiltin("Action(Back)")
|
|
elif self.isVisible(self.itemList): self.closeChannel(self.getFocusItems().get('citem'),self.getFocusItems().get('position'))
|
|
elif self.isVisible(self.chanList): self.closeManager()
|
|
|
|
|
|
def onFocus(self, controlId):
|
|
self.log('onFocus: controlId = %s'%(controlId))
|
|
|
|
|
|
def onClick(self, controlId):
|
|
focusItems = self.getFocusItems(controlId)
|
|
focusID = focusItems.get('retCntrl')
|
|
focusLabel = focusItems.get('label')
|
|
focusNumber = focusItems.get('number',0)
|
|
focusCitem = focusItems.get('citem')
|
|
focusPOS = focusItems.get('chpos',0)
|
|
autoTuned = focusNumber > CHANNEL_LIMIT
|
|
|
|
self.log('onClick: controlId = %s\nitems = %s'%(controlId,focusItems))
|
|
if controlId == 0: self.closeManager()
|
|
elif controlId == 5: self.buildChannelItem(focusCitem) #item list
|
|
elif controlId == 6:
|
|
if self.lockAutotune and autoTuned: DIALOG.notificationDialog(LANGUAGE(32064))
|
|
else: self.buildChannelItem(self.itemInput(focusItems.get('item')),focusItems.get('item').getProperty('key'))
|
|
elif controlId == 10: #logo button
|
|
if self.lockAutotune and autoTuned: DIALOG.notificationDialog(LANGUAGE(32064))
|
|
else: self.switchLogo(focusCitem,focusPOS)
|
|
elif controlId in [9001,9002,9003,9004]: #side buttons
|
|
if focusLabel == LANGUAGE(32059): self.saveChanges() #Save
|
|
elif focusLabel == LANGUAGE(32061): self.clearChannel(focusCitem) #Delete
|
|
elif focusLabel == LANGUAGE(32239): self.clearChannel(focusCitem,open=True)#Clear
|
|
elif focusLabel == LANGUAGE(32136): self.moveChannel(focusCitem,focusPOS) #Move
|
|
elif focusLabel == LANGUAGE(32062): #Close
|
|
if self.isVisible(self.itemList): self.closeChannel(focusCitem,focus=focusPOS)
|
|
elif self.isVisible(self.chanList): self.closeManager()
|
|
elif focusLabel == LANGUAGE(32060): #Cancel
|
|
if self.isVisible(self.itemList): self.closeChannel(focusCitem)
|
|
elif self.isVisible(self.chanList): self.closeManager()
|
|
elif focusLabel == LANGUAGE(32240): #Confirm
|
|
if self.isVisible(self.itemList): self.saveChannelItems(focusCitem)
|
|
elif self.isVisible(self.chanList): self.saveChanges()
|
|
elif focusLabel == LANGUAGE(32235): #Preview
|
|
if self.isVisible(self.itemList) and self.madeItemchange: self.closeChannel(focusCitem, open=True)
|
|
self.previewChannel(focusCitem, focusID)
|
|
|