add caching for bouquet number offsets (faster bouquet changing)
[enigma2.git] / lib / python / Screens / ChannelSelection.py
index 7f5e10227a0da7b95565be1e98cf8572af0c1b38..f25cc09585dfeee459d6abc5a438d0aa158dd3b2 100644 (file)
@@ -2,8 +2,9 @@ from Screen import Screen
 from Components.Button import Button
 from Components.ServiceList import ServiceList
 from Components.ActionMap import NumberActionMap, ActionMap
+from Components.MenuList import MenuList
 from EpgSelection import EPGSelection
-from enigma import eServiceReference, eEPGCache, eEPGCachePtr, eServiceCenter, eServiceCenterPtr, iMutableServiceListPtr, iStaticServiceInformationPtr, eTimer
+from enigma import eServiceReference, eEPGCache, eEPGCachePtr, eServiceCenter, eServiceCenterPtr, iMutableServiceListPtr, iStaticServiceInformationPtr, eTimer, eDVBDB
 from Components.config import config, configElement, ConfigSubsection, configText, currentConfigSelectionElement
 from Screens.FixedMenu import FixedMenu
 from Tools.NumericalTextInput import NumericalTextInput
@@ -11,40 +12,64 @@ from Components.NimManager import nimmanager
 from Components.ServiceName import ServiceName
 from Components.Clock import Clock
 from Components.EventInfo import EventInfo
+from ServiceReference import ServiceReference
 from re import *
+from os import remove
 
 import xml.dom.minidom
 
-class BouquetSelector(FixedMenu):
+class BouquetSelector(Screen):
        def __init__(self, session, bouquets, selectedFunc):
+               Screen.__init__(self, session)
+
                self.selectedFunc=selectedFunc
+
+               self["actions"] = ActionMap(["OkCancelActions"],
+                       {
+                               "ok": self.okbuttonClick,
+                               "cancel": self.cancelClick
+                       })
                entrys = [ ]
                for x in bouquets:
-                       entrys.append((x[0], self.bouquetSelected, x[1]))
-               FixedMenu.__init__(self, session, "Bouquetlist", entrys)
-               self.skinName = "Menu"
+                       entrys.append((x[0], x[1]))
+               self["menu"] = MenuList(entrys)
 
-       def bouquetSelected(self):
-               self.selectedFunc(self["menu"].getCurrent()[2])
+       def okbuttonClick(self):
+               self.selectedFunc(self["menu"].getCurrent()[1])
 
-class ChannelContextMenu(FixedMenu):
+       def cancelClick(self):
+               self.close(False)
+
+class ChannelContextMenu(Screen):
        def __init__(self, session, csel):
+               Screen.__init__(self, session)
                self.csel = csel
 
+               self["actions"] = ActionMap(["OkCancelActions"],
+                       {
+                               "ok": self.okbuttonClick,
+                               "cancel": self.cancelClick
+                       })
                menu = [ ]
 
                inBouquetRootList = csel.getRoot().getPath().find('FROM BOUQUET "bouquets.') != -1 #FIXME HACK
                inBouquet = csel.getMutableList() is not None
                haveBouquets = csel.bouquet_root.getPath().find('FROM BOUQUET "bouquets.') != -1
 
-               if not csel.bouquet_mark_edit and not csel.movemode and not inBouquetRootList:
-                       if (csel.getCurrentSelection().flags & eServiceReference.flagDirectory) != eServiceReference.flagDirectory:
-                               if haveBouquets:
-                                       menu.append((_("add service to bouquet"), self.addServiceToBouquetSelected))
-                               else:
-                                       menu.append((_("add service to favourites"), self.addServiceToBouquetSelected))
-                       if inBouquet:
-                               menu.append((_("remove service"), self.removeCurrentService))
+               if not csel.bouquet_mark_edit and not csel.movemode:
+                       if not inBouquetRootList:
+                               if (csel.getCurrentSelection().flags & eServiceReference.flagDirectory) != eServiceReference.flagDirectory:
+                                       if haveBouquets:
+                                               menu.append((_("add service to bouquet"), self.addServiceToBouquetSelected))
+                                       else:
+                                               menu.append((_("add service to favourites"), self.addServiceToBouquetSelected))
+                               elif haveBouquets:
+                                       if not inBouquet and csel.getCurrentSelection().getPath().find("PROVIDERS") == -1:
+                                               menu.append((_("copy to favourites"), csel.copyCurrentToBouquetList))
+                               if inBouquet:
+                                       menu.append((_("remove service"), self.removeCurrentService))
+                       elif haveBouquets:
+                               menu.append((_("remove bouquet"), csel.removeBouquet))
 
                if inBouquet: # current list is editable?
                        if not csel.bouquet_mark_edit:
@@ -65,10 +90,14 @@ class ChannelContextMenu(FixedMenu):
                                        menu.append((_("end favourites edit"), self.bouquetMarkEnd))
                                        menu.append((_("abort favourites edit"), self.bouquetMarkAbort))
 
-               menu.append((_("back"), self.close))
+               menu.append((_("back"), self.cancelClick))
+               self["menu"] = MenuList(menu)
+
+       def okbuttonClick(self):
+               self["menu"].getCurrent()[1]()
 
-               FixedMenu.__init__(self, session, _("Channel Selection"), menu)
-               self.skinName = "Menu"
+       def cancelClick(self):
+               self.close(False)
 
        def addServiceToBouquetSelected(self):
                bouquets = self.csel.getBouquetList()
@@ -77,15 +106,27 @@ class ChannelContextMenu(FixedMenu):
                else:
                        cnt = len(bouquets)
                if cnt > 1: # show bouquet list
-                       self.session.open(BouquetSelector, bouquets, self.addCurrentServiceToBouquet)
+                       self.session.openWithCallback(self.bouquetSelClosed, BouquetSelector, bouquets, self.addCurrentServiceToBouquet)
                elif cnt == 1: # add to only one existing bouquet
                        self.addCurrentServiceToBouquet(bouquets[0][1])
                else: #no bouquets in root.. so assume only one favourite list is used
                        self.addCurrentServiceToBouquet(self.csel.bouquet_root)
 
+       def bouquetSelClosed(self, recursive):
+               if recursive:
+                       self.close(False)
+
+       def copyCurrentToBouquetList(self):
+               self.csel.copyCurrentToBouquetList()
+               self.close()
+
+       def removeBouquet(self):
+               self.csel.removeBouquet()
+               self.close()
+
        def addCurrentServiceToBouquet(self, dest):
                self.csel.addCurrentServiceToBouquet(dest)
-               self.close()
+               self.close(True) # close bouquet selection
 
        def removeCurrentService(self):
                self.csel.removeCurrentService()
@@ -159,6 +200,66 @@ class ChannelSelectionEdit:
                        return list.startEdit()
                return None
 
+       def buildBouquetID(self, str):
+               tmp = str.lower()
+               name = ''
+               for c in tmp:
+                       if (c >= 'a' and c <= 'z') or (c >= '0' and c <= '9'):
+                               name += c
+                       else:
+                               name += '_'
+               return name
+
+       def copyCurrentToBouquetList(self):
+               provider = ServiceReference(self.getCurrentSelection())
+               serviceHandler = eServiceCenter.getInstance()
+               mutableBouquetList = serviceHandler.list(self.bouquet_root).startEdit()
+               if mutableBouquetList:
+                       providerName = provider.getServiceName()
+                       if self.mode == MODE_TV:
+                               str = '1:7:1:0:0:0:0:0:0:0:(type == 1) FROM BOUQUET \"userbouquet.%s.tv\" ORDER BY bouquet'%(self.buildBouquetID(providerName))
+                       else:
+                               str = '1:7:2:0:0:0:0:0:0:0:(type == 2) FROM BOUQUET \"userbouquet.%s.radio\" ORDER BY bouquet'%(self.buildBouquetID(providerName))
+                       new_bouquet_ref = eServiceReference(str)
+                       if not mutableBouquetList.addService(new_bouquet_ref):
+                               self.bouquetNumOffsetCache = { }
+                               mutableBouquetList.flushChanges()
+                               eDVBDB.getInstance().reloadBouquets()
+                               mutableBouquet = serviceHandler.list(new_bouquet_ref).startEdit()
+                               if mutableBouquet:
+                                       mutableBouquet.setListName(providerName)
+                                       list = [ ]
+                                       services = serviceHandler.list(provider.ref)
+                                       if not services is None:
+                                               if not services.getContent(list, True):
+                                                       for service in list:
+                                                               if mutableBouquet.addService(service):
+                                                                       print "add", service.toString(), "to new bouquet failed"
+                                                       mutableBouquet.flushChanges()
+                                               else:
+                                                       print "getContent failed"
+                                       else:
+                                               print "list provider", providerName, "failed"
+                               else:
+                                       print "get mutable list for new created bouquet failed"
+                       else:
+                               print "add", str, "to bouquets failed"
+               else:
+                       print "bouquetlist is not editable"
+
+       def removeBouquet(self):
+               refstr = self.getCurrentSelection().toString()
+               self.bouquetNumOffsetCache = { }
+               pos = refstr.find('FROM BOUQUET "')
+               if pos != -1:
+                       refstr = refstr[pos+14:]
+                       pos = refstr.find('"')
+                       if pos != -1:
+                               filename = '/etc/enigma2/' + refstr[:pos] # FIXMEEE !!! HARDCODED /etc/enigma2
+               self.removeCurrentService()
+               remove(filename)
+               eDVBDB.getInstance().reloadBouquets()
+
 #  multiple marked entry stuff ( edit mode, later multiepg selection )
        def startMarkedEdit(self):
                self.mutableList = self.getMutableList()
@@ -176,11 +277,12 @@ class ChannelSelectionEdit:
                self.__marked = self.servicelist.getRootServices()
                for x in self.__marked:
                        self.servicelist.addMarked(eServiceReference(x))
-               self.saved_root = self.getRoot()
+               self.savedPath = self.servicePath[:]
                self.showAllServices()
 
        def endMarkedEdit(self, abort):
                if not abort and self.mutableList is not None:
+                       self.bouquetNumOffsetCache = { }
                        new_marked = set(self.servicelist.getMarked())
                        old_marked = set(self.__marked)
                        removed = old_marked - new_marked
@@ -201,7 +303,9 @@ class ChannelSelectionEdit:
                self.mutableList = None
                self.instance.setTitle(self.saved_title)
                self.saved_title = None
-               self.setRoot(self.saved_root)
+               self.servicePath = self.savedPath[:]
+               del self.savedPath
+               self.setRoot(self.servicePath[len(self.servicePath-1)])
 
        def clearMarks(self):
                self.servicelist.clearMarks()
@@ -218,6 +322,7 @@ class ChannelSelectionEdit:
                mutableList = self.getMutableList()
                if ref.valid() and mutableList is not None:
                        if not mutableList.removeService(ref):
+                               self.bouquetNumOffsetCache = { }
                                currentIndex = self.servicelist.getCurrentIndex()
                                self.servicelist.moveDown()
                                if self.servicelist.getCurrentIndex() == currentIndex:
@@ -230,6 +335,7 @@ class ChannelSelectionEdit:
                mutableList = self.getMutableList(dest)
                if not mutableList is None:
                        if not mutableList.addService(self.servicelist.getCurrent()):
+                               self.bouquetNumOffsetCache = { }
                                mutableList.flushChanges()
                self.close()
 
@@ -238,11 +344,13 @@ class ChannelSelectionEdit:
                        if self.entry_marked:
                                self.toggleMoveMarked() # unmark current entry
                        self.movemode = False
-                       self.pathChangedDisabled = True # re-enable path change
+                       self.pathChangedDisabled = False # re-enable path change
                        self.mutableList.flushChanges() # FIXME add check if changes was made
                        self.mutableList = None
                        self.instance.setTitle(self.saved_title)
                        self.saved_title = None
+                       if self.getRoot() == self.bouquet_root:
+                               self.bouquetNumOffsetCache = { }
                else:
                        self.mutableList = self.getMutableList()
                        self.movemode = True
@@ -279,7 +387,7 @@ class ChannelSelectionBase(Screen):
 
                # this makes it much simple to implement a selectable radio or tv mode :)
                self.service_types_tv = '1:7:1:0:0:0:0:0:0:0:(type == 1) || (type == 17)'
-               self.service_types_radio = '1:7:1:0:0:0:0:0:0:0:(type == 2)'
+               self.service_types_radio = '1:7:2:0:0:0:0:0:0:0:(type == 2)'
 
                self["key_red"] = Button(_("All"))
                self["key_green"] = Button(_("Satellites"))
@@ -293,15 +401,20 @@ class ChannelSelectionBase(Screen):
 
                self.servicePathTV = [ ]
                self.servicePathRadio = [ ]
+               self.servicePath = None
 
                self.pathChangedDisabled = False
 
+               self.bouquetNumOffsetCache = { }
+
                self["ChannelSelectBaseActions"] = NumberActionMap(["ChannelSelectBaseActions", "NumberActions"],
                        {
                                "showFavourites": self.showFavourites,
                                "showAllServices": self.showAllServices,
                                "showProviders": self.showProviders,
                                "showSatellites": self.showSatellites,
+                               "nextBouquet": self.nextBouquet,
+                               "prevBouquet": self.prevBouquet,
                                "1": self.keyNumberGlobal,
                                "2": self.keyNumberGlobal,
                                "3": self.keyNumberGlobal,
@@ -322,29 +435,33 @@ class ChannelSelectionBase(Screen):
                return ref
 
        def getBouquetNumOffset(self, bouquet):
-               bouquet = self.appendDVBTypes(bouquet)
                if self.bouquet_root.getPath().find('FROM BOUQUET "bouquets.') == -1: #FIXME HACK
                        return 0
-               offsetCount = 0
-               serviceHandler = eServiceCenter.getInstance()
-               bouquetlist = serviceHandler.list(self.bouquet_root)
-               if not bouquetlist is None:
-                       while True:
-                               bouquetIterator = self.appendDVBTypes(bouquetlist.getNext())
-                               if not bouquetIterator.valid() or bouquetIterator == bouquet: #end of list or bouquet found
-                                       break
-                               if ((bouquetIterator.flags & eServiceReference.flagDirectory) != eServiceReference.flagDirectory):
-                                       continue
-                               servicelist = serviceHandler.list(bouquetIterator)
-                               if not servicelist is None:
-                                       while True:
-                                               serviceIterator = servicelist.getNext()
-                                               if not serviceIterator.valid(): #check if end of list
-                                                       break
-                                               if serviceIterator.flags: #playable services have no flags
-                                                       continue
-                                               offsetCount += 1
-               return offsetCount
+               bouquet = self.appendDVBTypes(bouquet)
+               try:
+                       return self.bouquetNumOffsetCache[bouquet.toString()]
+               except:
+                       offsetCount = 0
+                       serviceHandler = eServiceCenter.getInstance()
+                       bouquetlist = serviceHandler.list(self.bouquet_root)
+                       if not bouquetlist is None:
+                               while True:
+                                       bouquetIterator = self.appendDVBTypes(bouquetlist.getNext())
+                                       if not bouquetIterator.valid(): #end of list
+                                               break
+                                       self.bouquetNumOffsetCache[bouquetIterator.toString()]=offsetCount
+                                       if ((bouquetIterator.flags & eServiceReference.flagDirectory) != eServiceReference.flagDirectory):
+                                               continue
+                                       servicelist = serviceHandler.list(bouquetIterator)
+                                       if not servicelist is None:
+                                               while True:
+                                                       serviceIterator = servicelist.getNext()
+                                                       if not serviceIterator.valid(): #check if end of list
+                                                               break
+                                                       if serviceIterator.flags: #playable services have no flags
+                                                               continue
+                                                       offsetCount += 1
+               return self.bouquetNumOffsetCache.get(bouquet.toString(), offsetCount)
 
        def recallBouquetMode(self):
                if self.mode == MODE_TV:
@@ -369,6 +486,7 @@ class ChannelSelectionBase(Screen):
                title += " (TV)"
                self.instance.setTitle(title)
                self.mode = MODE_TV
+               self.servicePath = self.servicePathTV
                self.recallBouquetMode()
 
        def setRadioMode(self):
@@ -379,6 +497,7 @@ class ChannelSelectionBase(Screen):
                title += " (Radio)"
                self.instance.setTitle(title)
                self.mode = MODE_RADIO
+               self.servicePath = self.servicePathRadio
                self.recallBouquetMode()
 
        def setRoot(self, root, justSet=False):
@@ -402,52 +521,30 @@ class ChannelSelectionBase(Screen):
                self.servicelist.moveDown()
 
        def clearPath(self):
-               if self.mode == MODE_RADIO:
-                       self.servicePathRadio = [ ]
-               else:
-                       self.servicePathTV = [ ]
+               del self.servicePath[:]
 
        def enterPath(self, ref, justSet=False):
-               if self.mode == MODE_RADIO:
-                       self.servicePathRadio.append(ref)
-               else:
-                       self.servicePathTV.append(ref)
+               self.servicePath.append(ref)
                self.setRoot(ref, justSet)
 
        def pathUp(self, justSet=False):
-               if self.mode == MODE_TV:
-                       print "MODE_TV"
-                       prev = self.servicePathTV.pop()
-                       print "prev", prev.toString()
-                       length = len(self.servicePathTV)
-                       print "length", length
-                       if length:
-                               current = self.servicePathTV[length-1]
-                               print "current", current.toString()
-               else:
-                       prev = self.servicePathRadio.pop()
-                       length = len(self.servicePathRadio)
-                       if length:
-                               current = self.servicePathRadio[length-1]
+               prev = self.servicePath.pop()
+               length = len(self.servicePath)
+               if length:
+                       current = self.servicePath[length-1]
                self.setRoot(current, justSet)
                if not justSet:
-                       print "setCurrentSelection prev"
                        self.setCurrentSelection(prev)
                return prev
 
        def isBasePathEqual(self, ref):
-               if self.mode == MODE_RADIO and len(self.servicePathRadio) > 1 and self.servicePathRadio[0] == ref:
-                       return True
-               elif self.mode == MODE_TV and len(self.servicePathTV) > 1 and self.servicePathTV[0] == ref:
+               if len(self.servicePath) > 1 and self.servicePath[0] == ref:
                        return True
                return False
 
        def isPrevPathEqual(self, ref):
-               path = self.servicePathRadio
-               if self.mode == MODE_TV:
-                       path = self.servicePathTV
-               length = len(path)
-               if length > 1 and path[length-2] == ref:
+               length = len(self.servicePath)
+               if length > 1 and self.servicePath[length-2] == ref:
                        return True
                return False
 
@@ -521,14 +618,38 @@ class ChannelSelectionBase(Screen):
                                                self.clearPath()
                                                self.enterPath(ref)
 
+       def changeBouquet(self, direction):
+               if not self.pathChangedDisabled:
+                       if self.isBasePathEqual(self.bouquet_root):
+                               self.pathUp()
+                               if direction < 0:
+                                       self.moveUp()
+                               else:
+                                       self.moveDown()
+                               ref = self.getCurrentSelection()
+                               self.enterPath(ref)
+
+       def inBouquet(self):
+               return self.isBasePathEqual(self.bouquet_root)
+
+       def atBegin(self):
+               return self.servicelist.atBegin()
+
+       def atEnd(self):
+               return self.servicelist.atEnd()
+
+       def nextBouquet(self):
+               self.changeBouquet(+1)
+
+       def prevBouquet(self):
+               self.changeBouquet(-1)
+
        def showFavourites(self):
                if not self.pathChangedDisabled:
                        if not self.preEnterPath(self.bouquet_rootstr):
                                if self.isBasePathEqual(self.bouquet_root):
-                                       print "basepath equal"
                                        self.pathUp()
                                else:
-                                       print "basepath not equal"
                                        currentRoot = self.getRoot()
                                        if currentRoot is None or currentRoot != self.bouquet_root:
                                                self.clearPath()
@@ -536,8 +657,6 @@ class ChannelSelectionBase(Screen):
 
        def keyNumberGlobal(self, number):
                char = self.numericalTextInput.getKey(number)
-               print "You pressed number " + str(number)
-               print "You would go to character " + str(char)
                self.servicelist.moveToChar(char)
 
        def getRoot(self):
@@ -598,22 +717,21 @@ class ChannelSelection(ChannelSelectionBase, ChannelSelectionEdit, ChannelSelect
                                "cancel": self.cancel,
                                "ok": self.channelSelected,
                        })
-               self.onShown.append(self.onShow)
+               self.onShown.append(self.__onShown)
 
                self.lastChannelRootTimer = eTimer()
-               self.lastChannelRootTimer.timeout.get().append(self.onCreate)
+               self.lastChannelRootTimer.timeout.get().append(self.__onCreate)
                self.lastChannelRootTimer.start(100,True)
 
-       def onCreate(self):
+       def __onCreate(self):
                self.setTvMode()
-               self.servicePathTV = [ ]
                self.restoreRoot()
                lastservice=eServiceReference(config.tv.lastservice.value)
                if lastservice.valid():
                        self.setCurrentSelection(lastservice)
                        self.session.nav.playService(lastservice)
 
-       def onShow(self):
+       def __onShown(self):
                self.recallBouquetMode()
                ref = self.session.nav.getCurrentlyPlayingServiceReference()
                if ref is not None and ref.valid() and ref.getPath() == "":
@@ -654,7 +772,7 @@ class ChannelSelection(ChannelSelectionBase, ChannelSelectionEdit, ChannelSelect
                        config.tv.lastroot.save()
 
        def restoreRoot(self):
-               self.servicePathTV = [ ]
+               self.clearPath()
                re = compile('.+?;')
                tmp = re.findall(config.tv.lastroot.value)
                cnt = 0
@@ -758,7 +876,7 @@ class ChannelSelectionRadio(ChannelSelectionBase, ChannelSelectionEdit, ChannelS
                        config.radio.lastroot.save()
 
        def restoreRoot(self):
-               self.servicePathRadio = [ ]
+               self.clearPath()
                re = compile('.+?;')
                tmp = re.findall(config.radio.lastroot.value)
                cnt = 0
@@ -791,7 +909,7 @@ class ChannelSelectionRadio(ChannelSelectionBase, ChannelSelectionEdit, ChannelS
                        self.servicelist.setCurrent(lastservice)
                        self.session.nav.playService(lastservice)
                        self.servicelist.setPlayableIgnoreService(lastservice)
-               self.info.instance.show()
+               self.info.show()
 
        def channelSelected(self): # just return selected service
                ref = self.getCurrentSelection()
@@ -811,7 +929,7 @@ class ChannelSelectionRadio(ChannelSelectionBase, ChannelSelectionEdit, ChannelS
                        self.saveRoot()
 
        def closeRadio(self):
-               self.info.instance.hide()
+               self.info.hide()
                #set previous tv service
                lastservice=eServiceReference(config.tv.lastservice.value)
                self.session.nav.playService(lastservice)
@@ -821,7 +939,7 @@ class SimpleChannelSelection(ChannelSelectionBase):
        def __init__(self, session, title):
                ChannelSelectionBase.__init__(self, session)
                self.title = title
-               self.onShown.append(self.onExecCallback)
+               self.onShown.append(self.__onExecCallback)
 
                self["actions"] = ActionMap(["OkCancelActions", "TvRadioActions"],
                        {
@@ -831,7 +949,7 @@ class SimpleChannelSelection(ChannelSelectionBase):
                                "keyTV": self.setModeTv,
                        })
 
-       def onExecCallback(self):
+       def __onExecCallback(self):
                self.session.currentDialog.instance.setTitle(self.title)
                self.setModeTv()