react a little nicer to the sanity check (will try to get a rerun if we could not...
[enigma2-plugins.git] / autotimer / src / AutoTimer.py
1 # Plugins Config
2 from xml.dom.minidom import parse as minidom_parse
3 from os import path as os_path
4 from AutoTimerConfiguration import parseConfig, writeConfig
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 AutoTimerComponent
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 class AutoTimerIgnoreTimerException(Exception):
36         def __init__(self, cause):
37                 self.cause = cause
38
39         def __str__(self):
40                 return "[AutoTimer] " + str(self.cause)
41
42         def __repr__(self):
43                 return str(type(self))
44
45 class AutoTimer:
46         """Read and save xml configuration, query EPGCache"""
47
48         def __init__(self):
49                 # Keep EPGCache
50                 self.epgcache = eEPGCache.getInstance()
51
52                 # Initialize
53                 self.timers = []
54                 self.configMtime = -1
55                 self.uniqueTimerId = 0
56                 self.defaultTimer = AutoTimerComponent(
57                         0,              # Id
58                         "",             # Name
59                         "",             # Match
60                         True    # Enabled
61                 )
62
63 # Configuration
64
65         def readXml(self):
66                 # Abort if no config found
67                 if not os_path.exists(XML_CONFIG):
68                         print "[AutoTimer] No configuration file present"
69                         return
70
71                 # Parse if mtime differs from whats saved
72                 mtime = os_path.getmtime(XML_CONFIG)
73                 if mtime == self.configMtime:
74                         print "[AutoTimer] No changes in configuration, won't parse"
75                         return
76
77                 # Save current mtime
78                 self.configMtime = mtime
79
80                 # Parse Config
81                 dom = minidom_parse(XML_CONFIG)
82                 
83                 # Empty out timers and reset Ids
84                 del self.timers[:]
85                 self.uniqueTimerId = 0
86                 self.defaultTimer.clear(-1, True)
87
88                 # Get Config Element
89                 for configuration in dom.getElementsByTagName("autotimer"):
90                         parseConfig(
91                                 configuration,
92                                 self.timers,
93                                 configuration.getAttribute("version"),
94                                 self.uniqueTimerId,
95                                 self.defaultTimer
96                         )
97                         self.uniqueTimerId = len(self.timers)
98
99         def writeXml(self):
100                 writeConfig(XML_CONFIG, self.defaultTimer, self.timers)
101
102 # Manage List
103
104         def add(self, timer):
105                 self.timers.append(timer)
106
107         def getEnabledTimerList(self):
108                 return [x for x in self.timers if x.enabled]
109
110         def getTimerList(self):
111                 return self.timers
112
113         def getTupleTimerList(self):
114                 list = self.timers
115                 return [(x,) for x in list]
116
117         def getSortedTupleTimerList(self):
118                 list = self.timers[:]
119                 list.sort()
120                 return [(x,) for x in list]
121
122         def getUniqueId(self):
123                 self.uniqueTimerId += 1
124                 return self.uniqueTimerId
125
126         def remove(self, uniqueId):
127                 idx = 0
128                 for timer in self.timers:
129                         if timer.id == uniqueId:
130                                 self.timers.pop(idx)
131                                 return
132                         idx += 1
133
134         def set(self, timer):
135                 idx = 0
136                 for stimer in self.timers:
137                         if stimer == timer:
138                                 self.timers[idx] = timer
139                                 return
140                         idx += 1
141                 self.timers.append(timer)
142
143 # Main function
144
145         def parseEPG(self, simulateOnly = False):
146                 if NavigationInstance.instance is None:
147                         print "[AutoTimer] Navigation is not available, can't parse EPG"
148                         return (0, 0, 0, [])
149
150                 total = 0
151                 new = 0
152                 modified = 0
153                 timers = []
154
155                 self.readXml()
156
157                 # Save Recordings in a dict to speed things up a little
158                 # We include processed timers as we might search for duplicate descriptions
159                 recorddict = {}
160                 for timer in NavigationInstance.instance.RecordTimer.timer_list + NavigationInstance.instance.RecordTimer.processed_timers:
161                         if not recorddict.has_key(str(timer.service_ref)):
162                                 recorddict[str(timer.service_ref)] = [timer]
163                         else:
164                                 recorddict[str(timer.service_ref)].append(timer)
165
166                 # Iterate Timer
167                 for timer in self.getEnabledTimerList():
168                         # Search EPG, default to empty list
169                         ret = self.epgcache.search(('RI', 100, eEPGCache.PARTIAL_TITLE_SEARCH, timer.match, eEPGCache.NO_CASE_CHECK)) or []
170
171                         for serviceref, eit in ret:
172                                 eserviceref = eServiceReference(serviceref)
173
174                                 evt = self.epgcache.lookupEventId(eserviceref, eit)
175                                 if not evt:
176                                         print "[AutoTimer] Could not create Event!"
177                                         continue
178
179                                 # Try to determine real service (we always choose the last one)
180                                 n = evt.getNumOfLinkageServices()
181                                 if n > 0:
182                                         i = evt.getLinkageService(eserviceref, n-1)
183                                         serviceref = i.toString()
184
185                                 # Gather Information
186                                 name = evt.getEventName()
187                                 description = evt.getShortDescription()
188                                 begin = evt.getBeginTime()
189                                 duration = evt.getDuration()
190                                 end = begin + duration
191
192                                 # If event starts in less than 60 seconds skip it
193                                 if begin < time() + 60:
194                                         continue
195
196                                 # Convert begin time
197                                 timestamp = localtime(begin)
198
199                                 # Update timer
200                                 timer.update(begin, timestamp)
201
202                                 # Check Duration, Timespan and Excludes
203                                 if timer.checkServices(serviceref) \
204                                         or timer.checkDuration(duration) \
205                                         or timer.checkTimespan(timestamp) \
206                                         or timer.checkFilter(name, description,
207                                                 evt.getExtendedDescription(), str(timestamp.tm_wday)):
208                                         continue
209
210                                 if timer.hasOffset():
211                                         # Apply custom Offset
212                                         begin, end = timer.applyOffset(begin, end)
213                                 else:
214                                         # Apply E2 Offset
215                                         begin -= config.recording.margin_before.value * 60
216                                         end += config.recording.margin_after.value * 60
217
218
219                                 total += 1
220
221                                 # Append to timerlist and abort if simulating
222                                 timers.append((name, begin, end, serviceref, timer.name))
223                                 if simulateOnly:
224                                         continue
225
226                                 # Initialize
227                                 newEntry = None
228                                 isNew = False
229
230                                 # Check for double Timers
231                                 # We first check eit and if user wants us to guess event based on time
232                                 # we try this as backup. The allowed diff should be configurable though.
233                                 try:
234                                         for rtimer in recorddict.get(serviceref, []):
235                                                 if rtimer.eit == eit or config.plugins.autotimer.try_guessing.value and getTimeDiff(rtimer, begin, end) > ((duration/10)*8):
236                                                         newEntry = rtimer
237
238                                                         # Abort if we don't want to modify timers or timer is repeated
239                                                         if config.plugins.autotimer.refresh.value == "none" or newEntry.repeated:
240                                                                 raise AutoTimerIgnoreTimerException("Won't modify existing timer because either no modification allowed or repeated timer")
241
242                                                         if hasattr(newEntry, "isAutoTimer"):
243                                                                         print "[AutoTimer] Modifying existing AutoTimer!"
244                                                         else:
245                                                                 if config.plugins.autotimer.refresh.value != "all":
246                                                                         raise AutoTimerIgnoreTimerException("Won't modify existing timer because it's no timer set by us")
247                                                                 print "[AutoTimer] Warning, we're messing with a timer which might not have been set by us"
248
249                                                         modified += 1
250
251                                                         # Modify values saved in timer
252                                                         newEntry.name = name
253                                                         newEntry.description = description
254                                                         newEntry.begin = int(begin)
255                                                         newEntry.end = int(end)
256                                                         newEntry.service_ref = ServiceReference(serviceref)
257
258                                                         break
259                                                 elif timer.getAvoidDuplicateDescription() == 1 and rtimer.description == description:
260                                                                 raise AutoTimerIgnoreTimerException("We found a timer with same description, skipping event")
261                                         if newEntry is None and timer.getAvoidDuplicateDescription() == 2:
262                                                 for list in recorddict.values():
263                                                         for rtimer in list:
264                                                                 if rtimer.description == description:
265                                                                         raise AutoTimerIgnoreTimerException("We found a timer with same description, skipping event")
266
267                                 except AutoTimerIgnoreTimerException, etite:
268                                         print etite
269                                         continue
270
271                                 # Event not yet in Timers
272                                 if newEntry is None:
273                                         if timer.checkCounter(timestamp):
274                                                 continue
275
276                                         isNew = True
277
278                                         print "[AutoTimer] Adding an event."
279                                         newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, description, eit)
280
281                                         # Mark this entry as AutoTimer (only AutoTimers will have this Attribute set)
282                                         newEntry.isAutoTimer = True
283
284                                 # Apply afterEvent
285                                 if timer.hasAfterEvent():
286                                         afterEvent = timer.getAfterEventTimespan(localtime(end))
287                                         if afterEvent is None:
288                                                 afterEvent = timer.getAfterEvent()
289                                         if afterEvent is not None:
290                                                 newEntry.afterEvent = afterEvent
291
292                                 newEntry.dirname = timer.destination
293                                 newEntry.justplay = timer.justplay
294                                 newEntry.tags = timer.tags # This needs my enhanced tag support patch to work
295
296                                 if isNew:
297                                         if NavigationInstance.instance.RecordTimer.record(newEntry) is None:
298                                                 new += 1
299                                                 if recorddict.has_key(serviceref):
300                                                         recorddict[serviceref].append(newEntry)
301                                                 else:
302                                                         recorddict[serviceref] = [newEntry]
303                                 else:
304                                         # XXX: this won't perform a sanity check, but do we actually want to do so?
305                                         NavigationInstance.instance.RecordTimer.timeChanged(newEntry)
306
307                 return (total, new, modified, timers)
308