epgrefresh: check for background capability on every zap
[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 # Timer
14 from EPGRefreshTimer import epgrefreshtimer, EPGRefreshTimerEntry, checkTimespan
15
16 # To calculate next timer execution
17 from time import time
18
19 # Plugin Config
20 from xml.etree.cElementTree import parse as cet_parse
21 from Tools.XMLTools import stringToXML
22 from os import path as path
23
24 # We want a list of unique services
25 from EPGRefreshService import EPGRefreshService
26
27 from OrderedSet import OrderedSet
28
29 # Configuration
30 from Components.config import config
31
32 # MessageBox
33 from Screens.MessageBox import MessageBox
34 from Tools import Notifications
35
36 # ... II
37 from MainPictureAdapter import MainPictureAdapter
38 from PipAdapter import PipAdapter
39 from RecordAdapter import RecordAdapter
40
41 # Path to configuration
42 CONFIG = "/etc/enigma2/epgrefresh.xml"
43 XML_VERSION = "1"
44
45 class EPGRefresh:
46         """Simple Class to refresh EPGData"""
47
48         def __init__(self):
49                 # Initialize
50                 self.services = (OrderedSet(), OrderedSet())
51                 self.forcedScan = False
52                 self.session = None
53                 self.beginOfTimespan = 0
54                 self.refreshAdapter = None
55
56                 # Mtime of configuration files
57                 self.configMtime = -1
58
59                 # Read in Configuration
60                 self.readConfiguration()
61
62         def readConfiguration(self):
63                 # Check if file exists
64                 if not path.exists(CONFIG):
65                         return
66
67                 # Check if file did not change
68                 mtime = path.getmtime(CONFIG)
69                 if mtime == self.configMtime:
70                         return
71
72                 # Keep mtime
73                 self.configMtime = mtime
74
75                 # Empty out list
76                 self.services[0].clear()
77                 self.services[1].clear()
78
79                 # Open file
80                 configuration = cet_parse(CONFIG).getroot()
81                 version = configuration.get("version", None)
82                 if version is None:
83                         factor = 60
84                 else: #if version == "1"
85                         factor = 1
86
87                 # Add References
88                 for service in configuration.findall("service"):
89                         value = service.text
90                         if value:
91                                 # strip all after last : (custom name)
92                                 pos = value.rfind(':')
93                                 if pos != -1:
94                                         value = value[:pos+1]
95
96                                 duration = service.get('duration', None)
97                                 duration = duration and int(duration)*factor
98
99                                 self.services[0].add(EPGRefreshService(value, duration))
100                 for bouquet in configuration.findall("bouquet"):
101                         value = bouquet.text
102                         if value:
103                                 duration = bouquet.get('duration', None)
104                                 duration = duration and int(duration)
105                                 self.services[1].add(EPGRefreshService(value, duration))
106
107         def buildConfiguration(self, webif = False):
108                 list = ['<?xml version="1.0" ?>\n<epgrefresh version="', XML_VERSION, '">\n\n']
109
110                 if webif:
111                         for serviceref in self.services[0].union(self.services[1]):
112                                 ref = ServiceReference(str(serviceref))
113                                 list.extend((
114                                         ' <e2service>\n',
115                                         '  <e2servicereference>', str(serviceref), '</e2servicereference>\n',
116                                         '  <e2servicename>', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), '</e2servicename>\n',
117                                         ' </e2service>\n',
118                                 ))
119                 else:
120                         for service in self.services[0]:
121                                 ref = ServiceReference(service.sref)
122                                 list.extend((' <!--', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), '-->\n', ' <service'))
123                                 if service.duration is not None:
124                                         list.extend((' duration="', str(service.duration), '"'))
125                                 list.extend(('>', stringToXML(service.sref), '</service>\n'))
126                         for bouquet in self.services[1]:
127                                 ref = ServiceReference(bouquet.sref)
128                                 list.extend((' <!--', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), '-->\n', ' <bouquet'))
129                                 if bouquet.duration is not None:
130                                         list.extend((' duration="', str(bouquet.duration), '"'))
131                                 list.extend(('>', stringToXML(bouquet.sref), '</bouquet>\n'))
132
133                 list.append('\n</epgrefresh>')
134
135                 return list
136
137         def saveConfiguration(self):
138                 file = open(CONFIG, 'w')
139                 file.writelines(self.buildConfiguration())
140
141                 file.close()
142
143         def maybeStopAdapter(self):
144                 if self.refreshAdapter:
145                         self.refreshAdapter.stop()
146                         self.refreshAdapter = None
147
148         def forceRefresh(self, session = None):
149                 print("[EPGRefresh] Forcing start of EPGRefresh")
150                 if self.session is None:
151                         if session is not None:
152                                 self.session = session
153                         else:
154                                 return False
155
156                 self.forcedScan = True
157                 self.prepareRefresh()
158                 return True
159
160         def start(self, session = None):
161                 if session is not None:
162                         self.session = session
163
164                 epgrefreshtimer.setRefreshTimer(self.createWaitTimer)
165
166         def stop(self):
167                 print("[EPGRefresh] Stopping Timer")
168                 self.maybeStopAdapter()
169                 epgrefreshtimer.clear()
170
171         def addServices(self, fromList, toList, channelIds):
172                 for scanservice in fromList:
173                         service = eServiceReference(scanservice.sref)
174                         if not service.valid() \
175                                 or (service.flags & (eServiceReference.isMarker|eServiceReference.isDirectory)):
176
177                                 continue
178
179                         channelID = '%08x%04x%04x' % (
180                                 service.getUnsignedData(4), # NAMESPACE
181                                 service.getUnsignedData(2), # TSID
182                                 service.getUnsignedData(3), # ONID
183                         )
184
185                         if channelID not in channelIds:
186                                 toList.append(scanservice)
187                                 channelIds.append(channelID)
188
189         def prepareRefresh(self):
190                 print("[EPGRefresh] About to start refreshing EPG")
191
192                 # Maybe read in configuration
193                 try:
194                         self.readConfiguration()
195                 except Exception as e:
196                         print("[EPGRefresh] Error occured while reading in configuration:", e)
197
198                 # This will hold services which are not explicitely in our list
199                 additionalServices = []
200                 additionalBouquets = []
201
202                 # See if we are supposed to read in autotimer services
203                 if config.plugins.epgrefresh.inherit_autotimer.value:
204                         removeInstance = False
205                         try:
206                                 # Import Instance
207                                 from Plugins.Extensions.AutoTimer.plugin import autotimer
208
209                                 if autotimer is None:
210                                         removeInstance = True
211                                         # Create an instance
212                                         from Plugins.Extensions.AutoTimer.AutoTimer import AutoTimer
213                                         autotimer = AutoTimer()
214
215                                 # Read in configuration
216                                 autotimer.readXml()
217                         except Exception as e:
218                                 print("[EPGRefresh] Could not inherit AutoTimer Services:", e)
219                         else:
220                                 # Fetch services
221                                 for timer in autotimer.getEnabledTimerList():
222                                         additionalServices.extend([EPGRefreshService(x, None) for x in timer.services])
223                                         additionalBouquets.extend([EPGRefreshService(x, None) for x in timer.bouquets])
224                         finally:
225                                 # Remove instance if there wasn't one before
226                                 if removeInstance:
227                                         autotimer = None
228
229                 scanServices = []
230                 channelIdList = []
231                 self.addServices(self.services[0], scanServices, channelIdList)
232
233                 serviceHandler = eServiceCenter.getInstance()
234                 for bouquet in self.services[1].union(additionalBouquets):
235                         myref = eServiceReference(bouquet.sref)
236                         list = serviceHandler.list(myref)
237                         if list is not None:
238                                 while 1:
239                                         s = list.getNext()
240                                         # TODO: I wonder if its sane to assume we get services here (and not just new lists)
241                                         if s.valid():
242                                                 additionalServices.append(EPGRefreshService(s.toString(), None))
243                                         else:
244                                                 break
245                 del additionalBouquets[:]
246
247                 self.addServices(additionalServices, scanServices, channelIdList)
248                 del additionalServices[:]
249
250                 # Debug
251                 print("[EPGRefresh] Services we're going to scan:", ', '.join([repr(x) for x in scanServices]))
252
253                 self.maybeStopAdapter()
254                 # NOTE: start notification is handled in adapter initializer
255                 if config.plugins.epgrefresh.adapter.value.startswith("pip"):
256                         hidden = config.plugins.epgrefresh.adapter.value == "pip_hidden"
257                         refreshAdapter = PipAdapter(self.session, hide=hidden)
258                 elif config.plugins.epgrefresh.adapter.value.startswith("record"):
259                         refreshAdapter = RecordAdapter(self.session)
260                 else:
261                         refreshAdapter = MainPictureAdapter(self.session)
262
263                 if (not refreshAdapter.backgroundCapable and Screens.Standby.inStandby) or not refreshAdapter.prepare():
264                         print("[EPGRefresh] Adapter is not able to run in background or not available, falling back to MainPictureAdapter")
265                         refreshAdapter = MainPictureAdapter(self.session)
266                         refreshAdapter.prepare()
267                 self.refreshAdapter = refreshAdapter
268
269                 self.scanServices = scanServices
270                 self.refresh()
271
272         def cleanUp(self):
273                 config.plugins.epgrefresh.lastscan.value = int(time())
274                 config.plugins.epgrefresh.lastscan.save()
275
276                 # Eventually force autotimer to parse epg
277                 if config.plugins.epgrefresh.parse_autotimer.value:
278                         removeInstance = False
279                         try:
280                                 # Import Instance
281                                 from Plugins.Extensions.AutoTimer.plugin import autotimer
282
283                                 if autotimer is None:
284                                         removeInstance = True
285                                         # Create an instance
286                                         from Plugins.Extensions.AutoTimer.AutoTimer import AutoTimer
287                                         autotimer = AutoTimer()
288
289                                 # Parse EPG
290                                 autotimer.parseEPG()
291                         except Exception as e:
292                                 print("[EPGRefresh] Could not start AutoTimer:", e)
293                         finally:
294                                 # Remove instance if there wasn't one before
295                                 if removeInstance:
296                                         autotimer = None
297
298                 # shutdown if we're supposed to go to deepstandby and not recording
299                 if not self.forcedScan and config.plugins.epgrefresh.afterevent.value \
300                         and not Screens.Standby.inTryQuitMainloop:
301
302                         self.session.open(
303                                 Screens.Standby.TryQuitMainloop,
304                                 1
305                         )
306
307                 if not Screens.Standby.inStandby and not config.plugins.epgrefresh.background and config.plugins.epgrefresh.enablemessage.value:
308                         Notifications.AddNotification(MessageBox, _("EPG refresh finished."), type=MessageBox.TYPE_INFO, timeout=4)
309                 self.forcedScan = False
310                 epgrefreshtimer.cleanup()
311                 self.maybeStopAdapter()
312
313         def refresh(self):
314                 if self.forcedScan:
315                         self.nextService()
316                 else:
317                         # Abort if a scan finished later than our begin of timespan
318                         if self.beginOfTimespan < config.plugins.epgrefresh.lastscan.value:
319                                 return
320                         if config.plugins.epgrefresh.force.value \
321                                 or (Screens.Standby.inStandby and \
322                                         not self.session.nav.RecordTimer.isRecording()):
323
324                                 self.nextService()
325                         # We don't follow our rules here - If the Box is still in Standby and not recording we won't reach this line
326                         else:
327                                 if not checkTimespan(
328                                         config.plugins.epgrefresh.begin.value,
329                                         config.plugins.epgrefresh.end.value):
330
331                                         print("[EPGRefresh] Gone out of timespan while refreshing, sorry!")
332                                         self.cleanUp()
333                                 else:
334                                         print("[EPGRefresh] Box no longer in Standby or Recording started, rescheduling")
335
336                                         # Recheck later
337                                         epgrefreshtimer.add(EPGRefreshTimerEntry(
338                                                         time() + config.plugins.epgrefresh.delay_standby.value*60,
339                                                         self.refresh,
340                                                         nocheck = True)
341                                         )
342
343         def createWaitTimer(self):
344                 self.beginOfTimespan = time()
345
346                 # Add wait timer to epgrefreshtimer
347                 epgrefreshtimer.add(EPGRefreshTimerEntry(time() + 30, self.prepareRefresh))
348
349         def nextService(self):
350                 # Debug
351                 print("[EPGRefresh] Maybe zap to next service")
352
353                 try:
354                         # Get next reference
355                         service = self.scanServices.pop(0)
356                 except IndexError:
357                         # Debug
358                         print("[EPGRefresh] Done refreshing EPG")
359
360                         # Clean up
361                         self.cleanUp()
362                 else:
363                         # If the current adapter is unable to run in background and we are in fact in background now,
364                         # fall back to main picture
365                         if (not self.refreshAdapter.backgroundCapable and Screens.Standby.inStandby):
366                                 print("[EPGRefresh] Adapter is not able to run in background or not available, falling back to MainPictureAdapter")
367                                 self.maybeStopAdapter()
368                                 self.refreshAdapter = MainPictureAdapter(self.session)
369                                 self.refreshAdapter.prepare()
370
371                         # Play next service
372                         # XXX: we might want to check the return value
373                         self.refreshAdapter.play(eServiceReference(service.sref))
374
375                         # Start Timer
376                         delay = service.duration or config.plugins.epgrefresh.interval_seconds.value
377                         epgrefreshtimer.add(EPGRefreshTimerEntry(
378                                 time() + delay,
379                                 self.refresh,
380                                 nocheck = True)
381                         )
382
383 epgrefresh = EPGRefresh()