[FanControl2] too much deleted metainfo inserted again
[vuplus_dvbapp-plugin] / autotimer / src / AutoTimer.py
1 # Plugins Config
2 from xml.etree.cElementTree import parse as cet_parse
3 from os import path as os_path
4 from AutoTimerConfiguration import parseConfig, buildConfig
5
6 # Navigation (RecordTimer)
7 import NavigationInstance
8
9 # Timer
10 from ServiceReference import ServiceReference
11 from RecordTimer import RecordTimerEntry
12 from Components.TimerSanityCheck import TimerSanityCheck
13
14 # Timespan
15 from time import localtime, time
16
17 # EPGCache & Event
18 from enigma import eEPGCache, eServiceReference
19
20 # Enigma2 Config
21 from Components.config import config
22
23 # AutoTimer Component
24 from AutoTimerComponent import preferredAutoTimerComponent
25
26 XML_CONFIG = "/etc/enigma2/autotimer.xml"
27
28 def getTimeDiff(timer, begin, end):
29         if begin <= timer.begin <= end:
30                 return end - timer.begin
31         elif timer.begin <= begin <= timer.end:
32                 return timer.end - begin
33         return 0
34
35 typeMap = {
36         "exact": eEPGCache.EXAKT_TITLE_SEARCH,
37         "partial": eEPGCache.PARTIAL_TITLE_SEARCH
38 }
39
40 caseMap = {
41         "sensitive": eEPGCache.CASE_CHECK,
42         "insensitive": eEPGCache.NO_CASE_CHECK
43 }
44
45 class AutoTimerIgnoreTimerException(Exception):
46         def __init__(self, cause):
47                 self.cause = cause
48
49         def __str__(self):
50                 return "[AutoTimer] " + str(self.cause)
51
52         def __repr__(self):
53                 return str(type(self))
54
55 class AutoTimer:
56         """Read and save xml configuration, query EPGCache"""
57
58         def __init__(self):
59                 # Initialize
60                 self.timers = []
61                 self.configMtime = -1
62                 self.uniqueTimerId = 0
63                 self.defaultTimer = preferredAutoTimerComponent(
64                         0,              # Id
65                         "",             # Name
66                         "",             # Match
67                         True    # Enabled
68                 )
69
70 # Configuration
71
72         def readXml(self):
73                 # Abort if no config found
74                 if not os_path.exists(XML_CONFIG):
75                         print "[AutoTimer] No configuration file present"
76                         return
77
78                 # Parse if mtime differs from whats saved
79                 mtime = os_path.getmtime(XML_CONFIG)
80                 if mtime == self.configMtime:
81                         print "[AutoTimer] No changes in configuration, won't parse"
82                         return
83
84                 # Save current mtime
85                 self.configMtime = mtime
86
87                 # Parse Config
88                 configuration = cet_parse(XML_CONFIG).getroot()
89
90                 # Empty out timers and reset Ids
91                 del self.timers[:]
92                 self.defaultTimer.clear(-1, True)
93
94                 parseConfig(
95                         configuration,
96                         self.timers,
97                         configuration.get("version"),
98                         0,
99                         self.defaultTimer
100                 )
101                 self.uniqueTimerId = len(self.timers)
102
103         def getXml(self):
104                 return buildConfig(self.defaultTimer, self.timers, webif = True)
105
106         def writeXml(self):
107                 file = open(XML_CONFIG, 'w')
108                 file.writelines(buildConfig(self.defaultTimer, self.timers))
109                 file.close()
110
111 # Manage List
112
113         def add(self, timer):
114                 self.timers.append(timer)
115
116         def getEnabledTimerList(self):
117                 return [x for x in self.timers if x.enabled]
118
119         def getTimerList(self):
120                 return self.timers
121
122         def getTupleTimerList(self):
123                 list = self.timers
124                 return [(x,) for x in list]
125
126         def getSortedTupleTimerList(self):
127                 list = self.timers[:]
128                 list.sort()
129                 return [(x,) for x in list]
130
131         def getUniqueId(self):
132                 self.uniqueTimerId += 1
133                 return self.uniqueTimerId
134
135         def remove(self, uniqueId):
136                 idx = 0
137                 for timer in self.timers:
138                         if timer.id == uniqueId:
139                                 self.timers.pop(idx)
140                                 return
141                         idx += 1
142
143         def set(self, timer):
144                 idx = 0
145                 for stimer in self.timers:
146                         if stimer == timer:
147                                 self.timers[idx] = timer
148                                 return
149                         idx += 1
150                 self.timers.append(timer)
151
152 # Main function
153
154         def parseEPG(self, simulateOnly = False):
155                 if NavigationInstance.instance is None:
156                         print "[AutoTimer] Navigation is not available, can't parse EPG"
157                         return (0, 0, 0, [], [])
158
159                 total = 0
160                 new = 0
161                 modified = 0
162                 timers = []
163                 conflicting = []
164
165                 self.readXml()
166
167                 # Save Recordings in a dict to speed things up a little
168                 # We include processed timers as we might search for duplicate descriptions
169                 recorddict = {}
170                 for timer in NavigationInstance.instance.RecordTimer.timer_list + NavigationInstance.instance.RecordTimer.processed_timers:
171                         recorddict.setdefault(str(timer.service_ref), []).append(timer)
172
173                 # Iterate Timer
174                 for timer in self.getEnabledTimerList():
175                         # Workaround to allow search for umlauts if we know the encoding
176                         match = timer.match
177                         if timer.encoding != 'UTF-8':
178                                 try:
179                                         match = match.decode('UTF-8').encode(timer.encoding)
180                                 except UnicodeDecodeError:
181                                         pass
182
183                         # Search EPG, default to empty list
184                         epgcache = eEPGCache.getInstance()
185                         ret = epgcache.search(('RI', 500, typeMap[timer.searchType], match, caseMap[timer.searchCase])) or ()
186
187                         for serviceref, eit in ret:
188                                 eserviceref = eServiceReference(serviceref)
189
190                                 evt = epgcache.lookupEventId(eserviceref, eit)
191                                 if not evt:
192                                         print "[AutoTimer] Could not create Event!"
193                                         continue
194
195                                 # Try to determine real service (we always choose the last one)
196                                 n = evt.getNumOfLinkageServices()
197                                 if n > 0:
198                                         i = evt.getLinkageService(eserviceref, n-1)
199                                         serviceref = i.toString()
200
201                                 # Gather Information
202                                 name = evt.getEventName()
203                                 description = evt.getShortDescription()
204                                 begin = evt.getBeginTime()
205                                 duration = evt.getDuration()
206                                 end = begin + duration
207
208                                 # If event starts in less than 60 seconds skip it
209                                 if begin < time() + 60:
210                                         continue
211
212                                 # Convert begin time
213                                 timestamp = localtime(begin)
214
215                                 # Update timer
216                                 timer.update(begin, timestamp)
217
218                                 # Check Duration, Timespan and Excludes
219                                 if timer.checkServices(serviceref) \
220                                         or timer.checkDuration(duration) \
221                                         or timer.checkTimespan(timestamp) \
222                                         or timer.checkFilter(name, description,
223                                                 evt.getExtendedDescription(), str(timestamp.tm_wday)):
224                                         continue
225
226                                 if timer.hasOffset():
227                                         # Apply custom Offset
228                                         begin, end = timer.applyOffset(begin, end)
229                                 else:
230                                         # Apply E2 Offset
231                                         begin -= config.recording.margin_before.value * 60
232                                         end += config.recording.margin_after.value * 60
233
234                                 # Eventually change service to alternative
235                                 if timer.overrideAlternatives:
236                                         serviceref = timer.getAlternative(serviceref)
237
238                                 total += 1
239
240                                 # Append to timerlist and abort if simulating
241                                 timers.append((name, begin, end, serviceref, timer.name))
242                                 if simulateOnly:
243                                         continue
244
245                                 # Initialize
246                                 newEntry = None
247                                 oldExists = False
248
249                                 # Check for double Timers
250                                 # We first check eit and if user wants us to guess event based on time
251                                 # we try this as backup. The allowed diff should be configurable though.
252                                 for rtimer in recorddict.get(serviceref, ()):
253                                         if rtimer.eit == eit or config.plugins.autotimer.try_guessing.value and getTimeDiff(rtimer, begin, end) > ((duration/10)*8):
254                                                 oldExists = True
255
256                                                 # Abort if we don't want to modify timers or timer is repeated
257                                                 if config.plugins.autotimer.refresh.value == "none" or rtimer.repeated:
258                                                         print "[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer"
259                                                         break
260
261                                                 if hasattr(rtimer, "isAutoTimer"):
262                                                                 print "[AutoTimer] Modifying existing AutoTimer!"
263                                                 else:
264                                                         if config.plugins.autotimer.refresh.value != "all":
265                                                                 print "[AutoTimer] Won't modify existing timer because it's no timer set by us"
266                                                                 break
267
268                                                         print "[AutoTimer] Warning, we're messing with a timer which might not have been set by us"
269
270                                                 newEntry = rtimer
271                                                 modified += 1
272
273                                                 # Modify values saved in timer
274                                                 newEntry.name = name
275                                                 newEntry.description = description
276                                                 newEntry.begin = int(begin)
277                                                 newEntry.end = int(end)
278                                                 newEntry.service_ref = ServiceReference(serviceref)
279
280                                                 break
281                                         elif timer.avoidDuplicateDescription == 1 and not rtimer.disabled and rtimer.name == name and rtimer.description == description:
282                                                 oldExists = True
283                                                 print "[AutoTimer] We found a timer with same description, skipping event"
284                                                 break
285
286                                 # We found no timer we want to edit
287                                 if newEntry is None:
288                                         # But there is a match
289                                         if oldExists:
290                                                 continue
291
292                                         # We want to search for possible doubles
293                                         if timer.avoidDuplicateDescription == 2:
294                                                 # I thinks thats the fastest way to do this, though it's a little ugly
295                                                 try:
296                                                         for list in recorddict.values():
297                                                                 for rtimer in list:
298                                                                         if not rtimer.disabled and rtimer.name == name and rtimer.description == description:
299                                                                                 raise AutoTimerIgnoreTimerException("We found a timer with same description, skipping event")
300                                                 except AutoTimerIgnoreTimerException, etite:
301                                                         print etite
302                                                         continue
303
304                                         if timer.checkCounter(timestamp):
305                                                 continue
306
307                                         print "[AutoTimer] Adding an event."
308                                         newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, description, eit)
309
310                                         # Mark this entry as AutoTimer (only AutoTimers will have this Attribute set)
311                                         newEntry.isAutoTimer = True
312
313                                 # Apply afterEvent
314                                 if timer.hasAfterEvent():
315                                         afterEvent = timer.getAfterEventTimespan(localtime(end))
316                                         if afterEvent is None:
317                                                 afterEvent = timer.getAfterEvent()
318                                         if afterEvent is not None:
319                                                 newEntry.afterEvent = afterEvent
320
321                                 newEntry.dirname = timer.destination
322                                 newEntry.justplay = timer.justplay
323                                 newEntry.tags = timer.tags
324
325                                 if oldExists:
326                                         # XXX: this won't perform a sanity check, but do we actually want to do so?
327                                         NavigationInstance.instance.RecordTimer.timeChanged(newEntry)
328                                 else:
329                                         conflicts = NavigationInstance.instance.RecordTimer.record(newEntry)
330                                         if conflicts and config.plugins.autotimer.disabled_on_conflict.value:
331                                                 newEntry.disabled = True
332                                                 # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway
333                                                 conflicts = NavigationInstance.instance.RecordTimer.record(newEntry)
334                                                 conflicting.append((name, begin, end, serviceref, timer.name))
335                                         if conflicts is None:
336                                                 timer.decrementCounter()
337                                                 new += 1
338                                                 recorddict.setdefault(serviceref, []).append(newEntry)
339                                         else:
340                                                 conflicting.append((name, begin, end, serviceref, timer.name))
341
342                 return (total, new, modified, timers, conflicting)
343