fixes bug #381
[enigma2.git] / lib / python / Components / ParentalControl.py
1 from Components.config import config, ConfigSubsection, ConfigSelection, ConfigPIN, ConfigText, ConfigYesNo, ConfigSubList, ConfigInteger
2 #from Screens.ChannelSelection import service_types_tv
3 from Screens.InputBox import PinInput
4 from Screens.MessageBox import MessageBox
5 from Tools.BoundFunction import boundFunction
6 from ServiceReference import ServiceReference
7 from Tools import Notifications
8 from Tools.Directories import resolveFilename, SCOPE_CONFIG
9 from enigma import eTimer
10 import time
11
12 TYPE_SERVICE = "SERVICE"
13 TYPE_BOUQUETSERVICE = "BOUQUETSERVICE"
14 TYPE_BOUQUET = "BOUQUET"
15 LIST_BLACKLIST = "blacklist"
16 LIST_WHITELIST = "whitelist"
17
18 IMG_WHITESERVICE = LIST_WHITELIST + "-" + TYPE_SERVICE
19 IMG_WHITEBOUQUET = LIST_WHITELIST + "-" + TYPE_BOUQUET
20 IMG_BLACKSERVICE = LIST_BLACKLIST + "-" + TYPE_SERVICE
21 IMG_BLACKBOUQUET = LIST_BLACKLIST + "-" + TYPE_BOUQUET
22
23 def InitParentalControl():
24         config.ParentalControl = ConfigSubsection()
25         config.ParentalControl.configured = ConfigYesNo(default = False)
26         config.ParentalControl.mode = ConfigSelection(default = "simple", choices = [("simple", _("simple")), ("complex", _("complex"))])
27         config.ParentalControl.storeservicepin = ConfigSelection(default = "never", choices = [("never", _("never")), ("5", _("5 minutes")), ("30", _("30 minutes")), ("60", _("60 minutes")), ("standby", _("until standby/restart"))])
28         config.ParentalControl.storeservicepincancel = ConfigSelection(default = "never", choices = [("never", _("never")), ("5", _("5 minutes")), ("30", _("30 minutes")), ("60", _("60 minutes")), ("standby", _("until standby/restart"))])
29         config.ParentalControl.servicepinactive = ConfigYesNo(default = False)
30         config.ParentalControl.setuppinactive = ConfigYesNo(default = False)
31         config.ParentalControl.type = ConfigSelection(default = "blacklist", choices = [(LIST_WHITELIST, _("whitelist")), (LIST_BLACKLIST, _("blacklist"))])
32         config.ParentalControl.setuppin = ConfigPIN(default = -1)
33         
34         config.ParentalControl.retries = ConfigSubsection()
35         config.ParentalControl.retries.setuppin = ConfigSubsection()
36         config.ParentalControl.retries.setuppin.tries = ConfigInteger(default = 3)
37         config.ParentalControl.retries.setuppin.time = ConfigInteger(default = 3)       
38         config.ParentalControl.retries.servicepin = ConfigSubsection()
39         config.ParentalControl.retries.servicepin.tries = ConfigInteger(default = 3)
40         config.ParentalControl.retries.servicepin.time = ConfigInteger(default = 3)
41 #       config.ParentalControl.configured = configElement("config.ParentalControl.configured", configSelection, 1, (("yes", _("yes")), ("no", _("no"))))
42         #config.ParentalControl.mode = configElement("config.ParentalControl.mode", configSelection, 0, (("simple", _("simple")), ("complex", _("complex"))))
43         #config.ParentalControl.storeservicepin = configElement("config.ParentalControl.storeservicepin", configSelection, 0, (("never", _("never")), ("5_minutes", _("5 minutes")), ("30_minutes", _("30 minutes")), ("60_minutes", _("60 minutes")), ("restart", _("until restart"))))
44         #config.ParentalControl.servicepinactive = configElement("config.ParentalControl.servicepinactive", configSelection, 1, (("yes", _("yes")), ("no", _("no"))))
45         #config.ParentalControl.setuppinactive = configElement("config.ParentalControl.setuppinactive", configSelection, 1, (("yes", _("yes")), ("no", _("no"))))
46         #config.ParentalControl.type = configElement("config.ParentalControl.type", configSelection, 0, (("whitelist", _("whitelist")), ("blacklist", _("blacklist"))))
47         #config.ParentalControl.setuppin = configElement("config.ParentalControl.setuppin", configSequence, "0000", configSequenceArg().get("PINCODE", (4, "")))
48
49         config.ParentalControl.servicepin = ConfigSubList()
50
51         for i in (0, 1, 2):
52                 config.ParentalControl.servicepin.append(ConfigPIN(default = -1))
53                 #config.ParentalControl.servicepin.append(configElement("config.ParentalControl.servicepin.level" + str(i), configSequence, "0000", configSequenceArg().get("PINCODE", (4, ""))))
54
55 class ParentalControl:
56         def __init__(self):
57                 #Do not call open on init, because bouquets are not ready at that moment 
58 #               self.open()
59                 self.serviceLevel = {}
60                 #Instead: Use Flags to see, if we already initialized config and called open
61                 self.configInitialized = False
62                 self.filesOpened = False
63                 #This is the timer that is used to see, if the time for caching the pin is over
64                 #Of course we could also work without a timer and compare the times every
65                 #time we call isServicePlayable. But this might probably slow down zapping, 
66                 #That's why I decided to use a timer
67                 self.sessionPinTimer = eTimer()
68                 self.sessionPinTimer.callback.append(self.resetSessionPin)
69         
70         def serviceMethodWrapper(self, service, method, *args):
71                 #This method is used to call all functions that need a service as Parameter: 
72                 #It takes either a Service- Reference or a Bouquet- Reference and passes 
73                 #Either the service or all services contained in the bouquet to the method given
74                 #That way all other functions do not need to distinguish between service and bouquet. 
75                 if "FROM BOUQUET" in service:
76                         method( service , TYPE_BOUQUET , *args )
77                         servicelist = self.readServicesFromBouquet(service,"C")
78                         for ref in servicelist:
79                                 sRef = str(ref[0])
80                                 method( sRef , TYPE_BOUQUETSERVICE , *args )
81                 else:
82                         ref = ServiceReference(service)
83                         sRef = str(ref)
84                         method( sRef , TYPE_SERVICE , *args )
85         
86         def setServiceLevel(self, service, type, level):
87                 self.serviceLevel[service] = level
88
89         def isServicePlayable(self, ref, callback):
90                 if not config.ParentalControl.configured.value or not config.ParentalControl.servicepinactive.value:
91                         return True
92                 #Check if we already read the whitelists and blacklists. If not: call open
93                 if self.filesOpened == False:
94                         self.open()
95                 #Check if configuration has already been read or if the significant values have changed.
96                 #If true: read the configuration 
97                 if self.configInitialized == False or self.storeServicePin != config.ParentalControl.storeservicepin.value or self.storeServicePinCancel != config.ParentalControl.storeservicepincancel.value:
98                         self.getConfigValues()
99                 service = ref.toCompareString()
100                 if (config.ParentalControl.type.value == LIST_WHITELIST and not self.whitelist.has_key(service)) or (config.ParentalControl.type.value == LIST_BLACKLIST and self.blacklist.has_key(service)):
101                         #Check if the session pin is cached and return the cached value, if it is.
102                         if self.sessionPinCached == True:
103                                 #As we can cache successful pin- entries as well as canceled pin- entries,
104                                 #We give back the last action 
105                                 return self.sessionPinCachedValue
106                         self.callback = callback
107                         #Someone started to implement different levels of protection. Seems they were never completed
108                         #I did not throw out this code, although it is of no use at the moment
109                         levelNeeded = 0
110                         if self.serviceLevel.has_key(service):
111                                 levelNeeded = self.serviceLevel[service]
112                         pinList = self.getPinList()[:levelNeeded + 1]
113                         Notifications.AddNotificationWithCallback(boundFunction(self.servicePinEntered, ref), PinInput, triesEntry = config.ParentalControl.retries.servicepin, pinList = pinList, service = ServiceReference(ref).getServiceName(), title = _("this service is protected by a parental control pin"), windowTitle = _("Parental control"))
114                         return False
115                 else:
116                         return True
117                 
118         def protectService(self, service):
119                 if config.ParentalControl.type.value == LIST_WHITELIST:
120                         if self.whitelist.has_key(service):
121                                 self.serviceMethodWrapper(service, self.removeServiceFromList, self.whitelist)
122                                 #self.deleteWhitelistService(service)
123                 else: # blacklist
124                         if not self.blacklist.has_key(service):
125                                 self.serviceMethodWrapper(service, self.addServiceToList, self.blacklist)
126                                 #self.addBlacklistService(service)
127                 #print "whitelist:", self.whitelist
128                 #print "blacklist:", self.blacklist
129
130         def unProtectService(self, service):
131                 #print "unprotect"
132                 #print "config.ParentalControl.type.value:", config.ParentalControl.type.value
133                 if config.ParentalControl.type.value == LIST_WHITELIST:
134                         if not self.whitelist.has_key(service):
135                                 self.serviceMethodWrapper(service, self.addServiceToList, self.whitelist)
136                                 #self.addWhitelistService(service)
137                 else: # blacklist
138                         if self.blacklist.has_key(service):
139                                 self.serviceMethodWrapper(service, self.removeServiceFromList, self.blacklist)
140                                 #self.deleteBlacklistService(service)
141                 #print "whitelist:", self.whitelist
142                 #print "blacklist:", self.blacklist
143
144         def getProtectionLevel(self, service):
145                 if (config.ParentalControl.type.value == LIST_WHITELIST and not self.whitelist.has_key(service)) or (config.ParentalControl.type.value == LIST_BLACKLIST and self.blacklist.has_key(service)):
146                         if self.serviceLevel.has_key(service):
147                                 return self.serviceLevel[service]
148                         else:
149                                 return 0
150                 else:
151                         return -1
152
153         def getProtectionType(self, service):
154                 #New method used in ParentalControlList: This method does not only return
155                 #if a service is protected or not, it also returns, why (whitelist or blacklist, service or bouquet)
156                 if self.filesOpened == False:
157                         self.open()
158                 sImage = ""
159                 if (config.ParentalControl.type.value == LIST_WHITELIST):
160                         if self.whitelist.has_key(service):
161                                 if TYPE_SERVICE in self.whitelist[service]:
162                                         sImage = IMG_WHITESERVICE
163                                 else:
164                                         sImage = IMG_WHITEBOUQUET
165                 elif (config.ParentalControl.type.value == LIST_BLACKLIST):
166                         if self.blacklist.has_key(service):
167                                 if TYPE_SERVICE in self.blacklist[service]:
168                                         sImage = IMG_BLACKSERVICE
169                                 else:
170                                         sImage = IMG_BLACKBOUQUET
171                 bLocked = self.getProtectionLevel(service) != -1
172                 return (bLocked,sImage)
173         
174         def getConfigValues(self):      
175                 #Read all values from configuration 
176                 self.checkPinInterval = False
177                 self.checkPinIntervalCancel = False
178                 self.checkSessionPin = False
179                 self.checkSessionPinCancel = False
180                 
181                 self.sessionPinCached = False
182                 self.pinIntervalSeconds = 0
183                 self.pinIntervalSecondsCancel = 0
184
185                 self.storeServicePin = config.ParentalControl.storeservicepin.value
186                 self.storeServicePinCancel = config.ParentalControl.storeservicepincancel.value
187                 
188                 if self.storeServicePin == "never":
189                         pass
190                 elif self.storeServicePin == "standby":
191                         self.checkSessionPin = True
192                 else:
193                         self.checkPinInterval = True
194                         iMinutes = float(self.storeServicePin)
195                         iSeconds = iMinutes*60
196                         self.pinIntervalSeconds = iSeconds
197         
198                 if self.storeServicePinCancel == "never":
199                         pass
200                 elif self.storeServicePinCancel == "standby":
201                         self.checkSessionPinCancel = True
202                 else:
203                         self.checkPinIntervalCancel = True
204                         iMinutes = float(self.storeServicePinCancel)
205                         iSeconds = iMinutes*60
206                         self.pinIntervalSecondsCancel = iSeconds
207         
208                 self.configInitialized = True
209                 # Reset PIN cache on standby: Use StandbyCounter- Config- Callback
210                 config.misc.standbyCounter.addNotifier(self.standbyCounterCallback, initial_call = False)
211
212         def standbyCounterCallback(self, configElement):
213                 self.resetSessionPin()
214                 
215         def resetSessionPin(self):
216                 #Reset the session pin, stop the timer
217                 self.sessionPinCached = False
218                 self.sessionPinTimer.stop()
219
220         def getCurrentTimeStamp(self):
221                 return time.time()
222
223         def getPinList(self):
224                 return [ x.value for x in config.ParentalControl.servicepin ]
225                 
226         def servicePinEntered(self, service, result):
227
228                 if result is not None and result:
229                         #This is the new function of caching the service pin
230                         #save last session and time of last entered pin...
231                         if self.checkSessionPin == True:
232                                 self.sessionPinCached = True
233                                 self.sessionPinCachedValue = True
234                         if self.checkPinInterval == True:
235                                 self.sessionPinCached = True
236                                 self.sessionPinCachedValue = True
237                                 self.sessionPinTimer.start(self.pinIntervalSeconds*1000,1)
238                         self.callback(ref = service)
239                 else:
240                         #This is the new function of caching cancelling of service pin
241                         if result is not None:
242                                 Notifications.AddNotification(MessageBox,  _("The pin code you entered is wrong."), MessageBox.TYPE_ERROR)
243                         else:
244                                 if self.checkSessionPinCancel == True:
245                                         self.sessionPinCached = True
246                                         self.sessionPinCachedValue = False
247                                 if self.checkPinIntervalCancel == True:
248                                         self.sessionPinCached = True
249                                         self.sessionPinCachedValue = False
250                                         self.sessionPinTimer.start(self.pinIntervalSecondsCancel*1000,1) 
251                         
252         def saveListToFile(self,sWhichList):
253                 #Replaces saveWhiteList and saveBlackList: 
254                 #I don't like to have two functions with identical code...
255                 if sWhichList == LIST_BLACKLIST:
256                         vList = self.blacklist
257                 else:
258                         vList = self.whitelist
259                 file = open(resolveFilename(SCOPE_CONFIG, sWhichList), 'w')
260                 for sService,sType in vList.iteritems():
261                         #Only Services that are selected directly and Bouqets are saved. 
262                         #Services that are added by a bouquet are not saved. 
263                         #This is the reason for the change in self.whitelist and self.blacklist
264                         if TYPE_SERVICE in sType or TYPE_BOUQUET in sType:
265                                 file.write(str(sService) + "\n")
266                 file.close
267
268         def openListFromFile(self,sWhichList):
269                 #Replaces openWhiteList and openBlackList: 
270                 #I don't like to have two functions with identical code...
271                 if sWhichList == LIST_BLACKLIST:
272                         self.blacklist = {}
273                         vList = self.blacklist
274                 else:
275                         self.whitelist = {}
276                         vList = self.whitelist
277                 try:
278                         file = open(resolveFilename(SCOPE_CONFIG, sWhichList ), 'r')
279                         lines = file.readlines()
280                         for x in lines:
281                                 sPlain = x.strip()
282                                 self.serviceMethodWrapper(sPlain, self.addServiceToList, vList)
283                         file.close
284                 except:
285                         pass
286         
287         def addServiceToList(self, service, type, vList):
288                 #Replaces addWhitelistService and addBlacklistService
289                 #The lists are not only lists of service references any more. 
290                 #They are named lists with the service as key and an array of types as value:
291                 
292                 if vList.has_key(service):
293                         if not type in vList[service]:
294                                 vList[service].append(type)
295                 else:
296                         vList[service] = [type]
297         
298         def removeServiceFromList(self, service, type, vList):
299                 #Replaces deleteWhitelistService and deleteBlacklistService
300                 if vList.has_key(service):
301                         if type in vList[service]:
302                                 vList[service].remove(type)
303                         if not vList[service]:
304                                 del vList[service]
305                 if self.serviceLevel.has_key(service):
306                         self.serviceLevel.remove(service)
307                 
308         def readServicesFromBouquet(self,sBouquetSelection,formatstring):
309                 #This method gives back a list of services for a given bouquet
310                 from enigma import eServiceCenter, eServiceReference
311                 from Screens.ChannelSelection import service_types_tv
312                 serviceHandler = eServiceCenter.getInstance()
313                 refstr = sBouquetSelection
314                 root = eServiceReference(refstr)
315                 list = serviceHandler.list(root)
316                 if list is not None:
317                         services = list.getContent("CN", True) #(servicecomparestring, name)
318                         return services
319                 
320         def save(self):
321                 # we need to open the files in case we havent's read them yet
322                 if not self.filesOpened:
323                         self.open()
324                 self.saveListToFile(LIST_BLACKLIST)
325                 self.saveListToFile(LIST_WHITELIST)
326                 
327         def open(self):
328                 self.openListFromFile(LIST_BLACKLIST)
329                 self.openListFromFile(LIST_WHITELIST)
330                 self.filesOpened = True
331
332 parentalControl = ParentalControl()