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