its mandatory to call deleteDialog for the pip screen
[enigma2-plugins.git] / epgrefresh / src / EPGRefresh.py
1 # -*- coding: UTF-8 -*-
2 from __future__ import print_function
3
4 # To check if in Standby
5 import Screens.Standby
6
7 # eServiceReference
8 from enigma import eServiceReference, eServiceCenter
9
10 # ...
11 from ServiceReference import ServiceReference
12
13 from RecordTimer import RecordTimerEntry
14 from Components.ParentalControl import parentalControl
15
16 # Timer
17 from EPGRefreshTimer import epgrefreshtimer, EPGRefreshTimerEntry, checkTimespan
18
19 # To calculate next timer execution
20 from time import time
21
22 # Error-print
23 from traceback import print_exc
24 from sys import stdout
25
26 # Plugin Config
27 from xml.etree.cElementTree import parse as cet_parse
28 from Tools.XMLTools import stringToXML
29 from os import path as path
30
31 # We want a list of unique services
32 from EPGRefreshService import EPGRefreshService
33
34 from OrderedSet import OrderedSet
35
36 # Configuration
37 from Components.config import config
38
39 # MessageBox
40 from Screens.MessageBox import MessageBox
41 from Tools import Notifications
42 from Tools.BoundFunction import boundFunction
43
44 # ... II
45 from . import _, ENDNOTIFICATIONID, NOTIFICATIONDOMAIN
46 from MainPictureAdapter import MainPictureAdapter
47 from PipAdapter import PipAdapter
48 from RecordAdapter import RecordAdapter
49
50 # Path to configuration
51 CONFIG = "/etc/enigma2/epgrefresh.xml"
52 XML_VERSION = "1"
53
54 class EPGRefresh:
55         """Simple Class to refresh EPGData"""
56
57         def __init__(self):
58                 # Initialize
59                 self.services = (OrderedSet(), OrderedSet())
60                 self.forcedScan = False
61                 self.isrunning = False
62                 self.doStopRunningRefresh = False
63                 self.session = None
64                 self.beginOfTimespan = 0
65                 self.refreshAdapter = None
66                 # to call additional tasks (from other plugins)
67                 self.finishNotifiers = { }
68
69                 # Mtime of configuration files
70                 self.configMtime = -1
71                 
72                 # Todos after finish
73                 self._initFinishTodos()
74
75                 # Read in Configuration
76                 self.readConfiguration()
77
78         def _initFinishTodos(self):
79                 self.finishTodos = [self._ToDoCallAutotimer, self._ToDoAutotimerCalled, self._callFinishNotifiers, self.finish]
80         
81         def addFinishNotifier(self, notifier):
82                 if not callable(notifier):
83                         print("[EPGRefresh] notifier" + str(notifier) + " isn't callable")
84                         return
85                 self.finishNotifiers[str(notifier)] = notifier
86
87         def removeFinishNotifier(self, notifier):
88                 notifierKey = str(notifier)
89                 if self.finishNotifiers.has_key(notifierKey):
90                         self.finishNotifiers.pop(notifierKey)
91
92         def readConfiguration(self):
93                 # Check if file exists
94                 if not path.exists(CONFIG):
95                         return
96
97                 # Check if file did not change
98                 mtime = path.getmtime(CONFIG)
99                 if mtime == self.configMtime:
100                         return
101
102                 # Keep mtime
103                 self.configMtime = mtime
104
105                 # Empty out list
106                 self.services[0].clear()
107                 self.services[1].clear()
108
109                 # Open file
110                 configuration = cet_parse(CONFIG).getroot()
111                 version = configuration.get("version", None)
112                 if version is None:
113                         factor = 60
114                 else: #if version == "1"
115                         factor = 1
116
117                 # Add References
118                 for service in configuration.findall("service"):
119                         value = service.text
120                         if value:
121                                 # strip all after last : (custom name)
122                                 pos = value.rfind(':')
123                                 # don't split alternative service
124                                 if value.find('1:134:1') == -1 and pos != -1:
125                                         value = value[:pos+1]
126
127                                 duration = service.get('duration', None)
128                                 duration = duration and int(duration)*factor
129
130                                 self.services[0].add(EPGRefreshService(value, duration))
131                 for bouquet in configuration.findall("bouquet"):
132                         value = bouquet.text
133                         if value:
134                                 duration = bouquet.get('duration', None)
135                                 duration = duration and int(duration)
136                                 self.services[1].add(EPGRefreshService(value, duration))
137
138         def buildConfiguration(self, webif = False):
139                 list = ['<?xml version="1.0" ?>\n<epgrefresh version="', XML_VERSION, '">\n\n']
140
141                 if webif:
142                         for serviceref in self.services[0].union(self.services[1]):
143                                 ref = ServiceReference(str(serviceref))
144                                 list.extend((
145                                         ' <e2service>\n',
146                                         '  <e2servicereference>', str(serviceref), '</e2servicereference>\n',
147                                         '  <e2servicename>', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), '</e2servicename>\n',
148                                         ' </e2service>\n',
149                                 ))
150                 else:
151                         for service in self.services[0]:
152                                 ref = ServiceReference(service.sref)
153                                 list.extend((' <!--', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), '-->\n', ' <service'))
154                                 if service.duration is not None:
155                                         list.extend((' duration="', str(service.duration), '"'))
156                                 list.extend(('>', stringToXML(service.sref), '</service>\n'))
157                         for bouquet in self.services[1]:
158                                 ref = ServiceReference(bouquet.sref)
159                                 list.extend((' <!--', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), '-->\n', ' <bouquet'))
160                                 if bouquet.duration is not None:
161                                         list.extend((' duration="', str(bouquet.duration), '"'))
162                                 list.extend(('>', stringToXML(bouquet.sref), '</bouquet>\n'))
163
164                 list.append('\n</epgrefresh>')
165
166                 return list
167
168         def saveConfiguration(self):
169                 file = open(CONFIG, 'w')
170                 file.writelines(self.buildConfiguration())
171
172                 file.close()
173
174         def maybeStopAdapter(self):
175                 if self.refreshAdapter:
176                         self.refreshAdapter.stop()
177                         self.refreshAdapter = None
178
179         def forceRefresh(self, session = None):
180                 print("[EPGRefresh] Forcing start of EPGRefresh")
181                 if self.session is None:
182                         if session is not None:
183                                 self.session = session
184                         else:
185                                 return False
186
187                 self.forcedScan = True
188                 self.prepareRefresh()
189                 return True
190         
191         def stopRunningRefresh(self, session = None):
192                 print("[EPGRefresh] Forcing stop of EPGRefresh")
193                 if self.session is None:
194                         if session is not None:
195                                 self.session = session
196                         else:
197                                 return False
198                 self.doStopRunningRefresh = True
199
200         def start(self, session = None):
201                 if session is not None:
202                         self.session = session
203
204                 epgrefreshtimer.setRefreshTimer(self.createWaitTimer)
205
206         def stop(self):
207                 print("[EPGRefresh] Stopping Timer")
208                 self.maybeStopAdapter()
209                 epgrefreshtimer.clear()
210
211         def addServices(self, fromList, toList, channelIds):
212                 for scanservice in fromList:
213                         service = eServiceReference(scanservice.sref)
214                         if not service.valid() \
215                                 or (service.flags & (eServiceReference.isMarker|eServiceReference.isDirectory)):
216
217                                 continue
218
219                         channelID = '%08x%04x%04x' % (
220                                 service.getUnsignedData(4), # NAMESPACE
221                                 service.getUnsignedData(2), # TSID
222                                 service.getUnsignedData(3), # ONID
223                         )
224
225                         if channelID not in channelIds:
226                                 toList.append(scanservice)
227                                 channelIds.append(channelID)
228
229         def generateServicelist(self, services, bouquets):
230                 # This will hold services which are not explicitely in our list
231                 additionalServices = []
232                 additionalBouquets = []
233
234                 # See if we are supposed to read in autotimer services
235                 if config.plugins.epgrefresh.inherit_autotimer.value:
236                         try:
237                                 # Import Instance
238                                 from Plugins.Extensions.AutoTimer.plugin import autotimer
239
240                                 if autotimer is None:
241                                         # Create an instance
242                                         from Plugins.Extensions.AutoTimer.AutoTimer import AutoTimer
243                                         autotimer = AutoTimer()
244
245                                 # Read in configuration
246                                 autotimer.readXml()
247                         except Exception as e:
248                                 print("[EPGRefresh] Could not inherit AutoTimer Services:", e)
249                         else:
250                                 # Fetch services
251                                 for timer in autotimer.getEnabledTimerList():
252                                         additionalServices.extend([EPGRefreshService(x, None) for x in timer.services])
253                                         additionalBouquets.extend([EPGRefreshService(x, None) for x in timer.bouquets])
254
255                 scanServices = []
256                 channelIdList = []
257                 self.addServices(services, scanServices, channelIdList)
258
259                 serviceHandler = eServiceCenter.getInstance()
260                 for bouquet in bouquets.union(additionalBouquets):
261                         myref = eServiceReference(bouquet.sref)
262                         list = serviceHandler.list(myref)
263                         if list is not None:
264                                 while 1:
265                                         s = list.getNext()
266                                         # TODO: I wonder if its sane to assume we get services here (and not just new lists)
267                                         if s.valid():
268                                                 additionalServices.append(EPGRefreshService(s.toString(), None))
269                                         else:
270                                                 break
271                 del additionalBouquets[:]
272
273                 self.addServices(additionalServices, scanServices, channelIdList)
274                 del additionalServices[:]
275
276                 return scanServices
277
278         def isRunning(self):
279                 return self.isrunning
280         
281         def isRefreshAllowed(self):
282                 if self.isRunning():
283                         message = _("There is still a refresh running. The Operation isn't allowed at this moment.")
284                         try:
285                                 if self.session != None:
286                                         self.session.open(MessageBox, message, \
287                                                  MessageBox.TYPE_INFO, timeout=10)
288                         except:
289                                 print("[EPGRefresh] Error while opening Messagebox!")
290                                 print_exc(file=stdout)
291                                 Notifications.AddPopup(message, MessageBox.TYPE_INFO, 10, domain = NOTIFICATIONDOMAIN)
292                         return False
293                 return True
294
295         def prepareRefresh(self):
296                 if not self.isRefreshAllowed():
297                         return
298                 self.isrunning = True
299                 print("[EPGRefresh] About to start refreshing EPG")
300
301                 self._initFinishTodos()
302                 # Maybe read in configuration
303                 try:
304                         self.readConfiguration()
305                 except Exception as e:
306                         print("[EPGRefresh] Error occured while reading in configuration:", e)
307
308                 self.scanServices = self.generateServicelist(self.services[0], self.services[1])
309
310                 # Debug
311                 print("[EPGRefresh] Services we're going to scan:", ', '.join([repr(x) for x in self.scanServices]))
312
313                 self.maybeStopAdapter()
314                 # NOTE: start notification is handled in adapter initializer
315                 if config.plugins.epgrefresh.adapter.value.startswith("pip"):
316                         hidden = config.plugins.epgrefresh.adapter.value == "pip_hidden"
317                         refreshAdapter = PipAdapter(self.session, hide=hidden)
318                 elif config.plugins.epgrefresh.adapter.value.startswith("record"):
319                         refreshAdapter = RecordAdapter(self.session)
320                 else:
321                         refreshAdapter = MainPictureAdapter(self.session)
322
323                 if (not refreshAdapter.backgroundCapable and Screens.Standby.inStandby) or not refreshAdapter.prepare():
324                         print("[EPGRefresh] Adapter is not able to run in background or not available, falling back to MainPictureAdapter")
325                         refreshAdapter = MainPictureAdapter(self.session)
326                         refreshAdapter.prepare()
327                 self.refreshAdapter = refreshAdapter
328
329                 try:
330                         from plugin import AdjustExtensionsmenu, extStopDescriptor, extPendingServDescriptor, extRunDescriptor
331                         AdjustExtensionsmenu(True, extPendingServDescriptor)
332                         AdjustExtensionsmenu(True, extStopDescriptor)
333                         AdjustExtensionsmenu(False, extRunDescriptor)
334                 except:
335                         print("[EPGRefresh] Error while adding 'Stop Running EPG-Refresh' to Extensionmenu")
336                         print_exc(file=stdout)
337                         
338                 self.refresh()
339
340         def cleanUp(self):
341                 print("[EPGRefresh] Debug: Cleanup")
342                 config.plugins.epgrefresh.lastscan.value = int(time())
343                 config.plugins.epgrefresh.lastscan.save()
344                 self.doStopRunningRefresh = False
345                 
346                 try:
347                         from plugin import AdjustExtensionsmenu, housekeepingExtensionsmenu, extStopDescriptor, extPendingServDescriptor
348                         AdjustExtensionsmenu(False, extPendingServDescriptor)
349                         AdjustExtensionsmenu(False, extStopDescriptor)
350                         housekeepingExtensionsmenu(config.plugins.epgrefresh.show_run_in_extensionsmenu, force=True)
351                 except:
352                         print("[EPGRefresh] Error while removing 'Stop Running EPG-Refresh' to Extensionmenu:")
353                         print_exc(file=stdout)
354                 
355                 # Start Todo-Chain
356                 self._nextTodo()
357
358         def _nextTodo(self, *args, **kwargs):
359                 print("[EPGRefresh] Debug: Calling nextTodo")
360                 if len(self.finishTodos) > 0:
361                         finishTodo = self.finishTodos.pop(0)
362                         print("[EPGRefresh] Debug: Call " + str(finishTodo))
363                         finishTodo(*args, **kwargs)
364                 
365
366         def _ToDoCallAutotimer(self):
367                 if config.plugins.epgrefresh.parse_autotimer.value != "never":
368                         if config.plugins.epgrefresh.parse_autotimer.value in ("ask_yes", "ask_no"):
369                                 defaultanswer = True if config.plugins.epgrefresh.parse_autotimer.value == "ask_yes" else False
370                                 if self.forcedScan:
371                                         # only if we are interactive
372                                         Notifications.AddNotificationWithCallback(self._ToDoCallAutotimerCB, MessageBox, \
373                                                 text = _("EPG refresh finished.\nShould AutoTimer be search for new matches?"), \
374                                                 type = MessageBox.TYPE_YESNO, default = defaultanswer, timeout = 10, domain = NOTIFICATIONDOMAIN)
375                                 else:
376                                         self._ToDoCallAutotimerCB(parseAT=defaultanswer)
377                         else:
378                                 if self.forcedScan\
379                                         and config.plugins.epgrefresh.parse_autotimer.value == "bg_only":
380                                         self._nextTodo()
381                                 else:
382                                         # config.parse_autotimer = always / bg_only
383                                         self._ToDoCallAutotimerCB(parseAT=True)
384                 else:
385                         self._nextTodo()
386
387         def _ToDoCallAutotimerCB(self, parseAT):
388                 print("[EPGRefresh] Debug: Call AutoTimer: " + str(parseAT))
389                 if parseAT:
390                         try:
391                                 # Import Instance
392                                 from Plugins.Extensions.AutoTimer.plugin import autotimer
393         
394                                 if autotimer is None:
395                                         # Create an instance
396                                         from Plugins.Extensions.AutoTimer.AutoTimer import AutoTimer
397                                         autotimer = AutoTimer()
398         
399                                 # Parse EPG
400                                 autotimer.parseEPGAsync(simulateOnly=False).addCallback(self._nextTodo).addErrback(self._autotimerErrback)
401                         except:
402                                 from traceback import format_exc
403                                 print("[EPGRefresh] Could not start AutoTimer:" + str(format_exc()))
404                 else:
405                         self._nextTodo()
406         
407         def _autotimerErrback(self, failure):
408                 print("[EPGRefresh] Debug: AutoTimer failed:" + str(failure))
409                 if config.plugins.epgrefresh.enablemessage.value:
410                         Notifications.AddPopup(_("AutoTimer failed with error %s") % (str(failure)), \
411                                 MessageBox.TYPE_ERROR, 10, domain = NOTIFICATIONDOMAIN)
412                 self._nextTodo()
413
414         def _ToDoAutotimerCalled(self, *args, **kwargs):
415                 if config.plugins.epgrefresh.enablemessage.value:
416                         if len(args):
417                                 # Autotimer has been started
418                                 ret=args[0]
419                                 Notifications.AddPopup(_("Found a total of %d matching Events.\n%d Timer were added and\n%d modified,\n%d conflicts encountered,\n%d similars added.") \
420                                         % (ret[0], ret[1], ret[2], len(ret[4]), len(ret[5])),
421                                         MessageBox.TYPE_INFO, 10, domain = NOTIFICATIONDOMAIN)
422                 self._nextTodo()
423         
424         def _callFinishNotifiers(self, *args, **kwargs):
425                 for notifier in self.finishNotifiers.keys():
426                         print("[EPGRefresh] Debug: call " + str(notifier))
427                         self.finishNotifiers[notifier]()
428                 self._nextTodo()
429         
430         def finish(self, *args, **kwargs):
431                 print("[EPGRefresh] Debug: Refresh finished!")
432                 if config.plugins.epgrefresh.enablemessage.value:
433                         Notifications.AddPopup(_("EPG refresh finished."), MessageBox.TYPE_INFO, 4, ENDNOTIFICATIONID, domain = NOTIFICATIONDOMAIN)
434                 epgrefreshtimer.cleanup()
435                 self.maybeStopAdapter()
436                 
437                 # shutdown if we're supposed to go to deepstandby and not recording
438                 if not self.forcedScan and config.plugins.epgrefresh.afterevent.value \
439                         and not Screens.Standby.inTryQuitMainloop:
440                         self.forcedScan = False
441                         if Screens.Standby.inStandby:
442                                 RecordTimerEntry.TryQuitMainloop()
443                         else:
444                                 Notifications.AddNotificationWithID("Shutdown", Screens.Standby.TryQuitMainloop, 1, domain = NOTIFICATIONDOMAIN)
445                 self.forcedScan = False
446                 self.isrunning = False
447                 self._nextTodo()
448                 
449         def refresh(self):
450                 if self.doStopRunningRefresh:
451                         self.cleanUp()
452                         return
453                 
454                 if self.forcedScan:
455                         self.nextService()
456                 else:
457                         # Abort if a scan finished later than our begin of timespan
458                         if self.beginOfTimespan < config.plugins.epgrefresh.lastscan.value:
459                                 return
460                         if config.plugins.epgrefresh.force.value \
461                                 or (Screens.Standby.inStandby and \
462                                         not self.session.nav.RecordTimer.isRecording()):
463
464                                 self.nextService()
465                         # We don't follow our rules here - If the Box is still in Standby and not recording we won't reach this line
466                         else:
467                                 if not checkTimespan(
468                                         config.plugins.epgrefresh.begin.value,
469                                         config.plugins.epgrefresh.end.value):
470
471                                         print("[EPGRefresh] Gone out of timespan while refreshing, sorry!")
472                                         self.cleanUp()
473                                 else:
474                                         print("[EPGRefresh] Box no longer in Standby or Recording started, rescheduling")
475
476                                         # Recheck later
477                                         epgrefreshtimer.add(EPGRefreshTimerEntry(
478                                                         time() + config.plugins.epgrefresh.delay_standby.value*60,
479                                                         self.refresh,
480                                                         nocheck = True)
481                                         )
482
483         def createWaitTimer(self):
484                 self.beginOfTimespan = time()
485
486                 # Add wait timer to epgrefreshtimer
487                 epgrefreshtimer.add(EPGRefreshTimerEntry(time() + 30, self.prepareRefresh))
488
489         def isServiceProtected(self, service):
490                 if not config.ParentalControl.configured.value \
491                         or not config.ParentalControl.servicepinactive.value:
492                         print("[EPGRefresh] DEBUG: ParentalControl not configured")
493                         return False
494                 print("[EPGRefresh] DEBUG: ParentalControl ProtectionLevel:" + str(parentalControl.getProtectionLevel(str(service))))
495                 return parentalControl.getProtectionLevel(str(service)) != -1
496
497         def nextService(self):
498                 # Debug
499                 print("[EPGRefresh] Maybe zap to next service")
500
501                 try:
502                         # Get next reference
503                         service = self.scanServices.pop(0)
504                 except IndexError:
505                         # Debug
506                         print("[EPGRefresh] Done refreshing EPG")
507
508                         # Clean up
509                         self.cleanUp()
510                 else:
511                         if self.isServiceProtected(service):
512                                 if (not self.forcedScan) or config.plugins.epgrefresh.skipProtectedServices.value == "always":
513                                         print("[EPGRefresh] Service is protected, skipping!")
514                                         self.refresh()
515                                         return
516                         
517                         # If the current adapter is unable to run in background and we are in fact in background now,
518                         # fall back to main picture
519                         if (not self.refreshAdapter.backgroundCapable and Screens.Standby.inStandby):
520                                 print("[EPGRefresh] Adapter is not able to run in background or not available, falling back to MainPictureAdapter")
521                                 self.maybeStopAdapter()
522                                 self.refreshAdapter = MainPictureAdapter(self.session)
523                                 self.refreshAdapter.prepare()
524
525                         # Play next service
526                         # XXX: we might want to check the return value
527                         self.refreshAdapter.play(eServiceReference(service.sref))
528
529                         # Start Timer
530                         delay = service.duration or config.plugins.epgrefresh.interval_seconds.value
531                         epgrefreshtimer.add(EPGRefreshTimerEntry(
532                                 time() + delay,
533                                 self.refresh,
534                                 nocheck = True)
535                         )
536         
537         def showPendingServices(self, session):
538                 LISTMAX = 10
539                 servcounter = 0
540                 try:
541                         servtxt = ""
542                         for service in self.scanServices:
543                                 if self.isServiceProtected(service):
544                                         if not self.forcedScan or config.plugins.epgrefresh.skipProtectedServices.value == "always":
545                                                 continue
546                                 if servcounter <= LISTMAX:
547                                         ref = ServiceReference(service.sref)
548                                         txt = ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')
549                                         servtxt = servtxt + str(txt) + "\n"
550                                 servcounter = servcounter + 1
551                         
552                         if servcounter > LISTMAX:
553                                 servtxt = servtxt + _("%d more services") % (servcounter)
554                         session.open(MessageBox, _("Following Services have to be scanned:") \
555                                 + "\n" + servtxt, MessageBox.TYPE_INFO)
556                 except:
557                         print("[EPGRefresh] showPendingServices Error!")
558                         print_exc(file=stdout)
559
560 epgrefresh = EPGRefresh()