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