Add option to enable or disable the timer conflict detection
[openblackhole/openblackhole-enigma2.git] / lib / python / Components / TimerSanityCheck.py
1 import NavigationInstance
2 from config import config
3 from time import localtime, mktime, gmtime
4 from ServiceReference import ServiceReference
5 from enigma import iServiceInformation, eServiceCenter, eServiceReference, getBestPlayableServiceReference
6 from timer import TimerEntry
7
8 class TimerSanityCheck:
9         def __init__(self, timerlist, newtimer=None):
10                 self.localtimediff = 25*3600 - mktime(gmtime(25*3600))
11                 self.timerlist = timerlist
12                 self.newtimer = newtimer
13                 self.simultimer = []
14                 self.rep_eventlist = []
15                 self.nrep_eventlist = []
16                 self.bflag = -1
17                 self.eflag = 1
18
19         def check(self, ext_timer=1):
20                 if not config.usage.timer_sanity_check_enabled.value:
21                         return True
22                 if ext_timer != 1:
23                         self.newtimer = ext_timer
24                 if self.newtimer is None:
25                         self.simultimer = []
26                 else:
27                         self.simultimer = [ self.newtimer ]
28                 return self.checkTimerlist()
29
30         def getSimulTimerList(self):
31                 return self.simultimer
32
33         def doubleCheck(self):
34                 if self.newtimer is not None and self.newtimer.service_ref.ref.valid():
35                         self.simultimer = [ self.newtimer ]
36                         for timer in self.timerlist:
37                                 if timer == self.newtimer:
38                                         return True
39                                 if self.newtimer.begin >= timer.begin and self.newtimer.end <= timer.end:
40                                         if timer.justplay and not self.newtimer.justplay:
41                                                 continue
42                                         if timer.service_ref.ref.flags & eServiceReference.isGroup:
43                                                 if self.newtimer.service_ref.ref.flags & eServiceReference.isGroup and timer.service_ref.ref.getPath() == self.newtimer.service_ref.ref.getPath():
44                                                         return True
45                                                 continue
46                                         getUnsignedDataRef1 = timer.service_ref.ref.getUnsignedData
47                                         getUnsignedDataRef2 = self.newtimer.service_ref.ref.getUnsignedData
48                                         for x in (1, 2, 3, 4):
49                                                 if getUnsignedDataRef1(x) != getUnsignedDataRef2(x):
50                                                         break;
51                                         else:
52                                                 return True
53                 return False
54
55         def checkTimerlist(self, ext_timer=1):
56                 #with special service for external plugins
57                 # Entries in eventlist
58                 # timeindex
59                 # BeginEndFlag 1 for begin, -1 for end
60                 # index -1 for the new Timer, 0..n index of the existing timers
61                 # count of running timers
62
63                 serviceHandler = eServiceCenter.getInstance()
64 # create a list with all start and end times
65 # split it into recurring and singleshot timers
66
67 ##################################################################################
68 # process the new timer
69                 self.rep_eventlist = []
70                 self.nrep_eventlist = []
71                 if ext_timer != 1:
72                         self.newtimer = ext_timer
73                 if (self.newtimer is not None) and (not self.newtimer.disabled):
74                         if not self.newtimer.service_ref.ref.valid():
75                                 return False
76                         rflags = self.newtimer.repeated
77                         rflags = ((rflags & 0x7F)>> 3)|((rflags & 0x07)<<4)
78                         if rflags:
79                                 begin = self.newtimer.begin % 86400 # map to first day
80                                 if (self.localtimediff > 0) and ((begin + self.localtimediff) > 86400):
81                                         rflags = ((rflags >> 1)& 0x3F)|((rflags << 6)& 0x40)
82                                 elif (self.localtimediff < 0) and (begin < self.localtimediff):
83                                         rflags = ((rflags << 1)& 0x7E)|((rflags >> 6)& 0x01)
84                                 while rflags: # then arrange on the week
85                                         if rflags & 1:
86                                                 self.rep_eventlist.append((begin, -1))
87                                         begin += 86400
88                                         rflags >>= 1
89                         else:
90                                 self.nrep_eventlist.extend([(self.newtimer.begin,self.bflag,-1),(self.newtimer.end,self.eflag,-1)])
91
92 ##################################################################################
93 # now process existing timers
94                 idx = 0
95                 for timer in self.timerlist:
96                         if (timer != self.newtimer) and (not timer.disabled):
97                                 if timer.repeated:
98                                         rflags = timer.repeated
99                                         rflags = ((rflags & 0x7F)>> 3)|((rflags & 0x07)<<4)
100                                         begin = timer.begin % 86400 # map all to first day
101                                         if (self.localtimediff > 0) and ((begin + self.localtimediff) > 86400):
102                                                 rflags = ((rflags >> 1)& 0x3F)|((rflags << 6)& 0x40)
103                                         elif (self.localtimediff < 0) and (begin < self.localtimediff):
104                                                 rflags = ((rflags << 1)& 0x7E)|((rflags >> 6)& 0x01)
105                                         while rflags:
106                                                 if rflags & 1:
107                                                         self.rep_eventlist.append((begin, idx))
108                                                 begin += 86400
109                                                 rflags >>= 1
110                                 elif timer.state < TimerEntry.StateEnded:
111                                         self.nrep_eventlist.extend([(timer.begin,self.bflag,idx),(timer.end,self.eflag,idx)])
112                         idx += 1
113
114 ################################################################################
115 # journalize timer repeations
116                 if self.nrep_eventlist:
117                         interval_begin = min(self.nrep_eventlist)[0]
118                         interval_end = max(self.nrep_eventlist)[0]
119                         offset_0 = interval_begin - (interval_begin % 604800)
120                         weeks = (interval_end - offset_0) / 604800
121                         if ((interval_end - offset_0) % 604800):
122                                 weeks += 1
123                         for cnt in range(weeks):
124                                 for event in self.rep_eventlist:
125                                         if event[1] == -1: # -1 is the identifier of the changed timer
126                                                 event_begin = self.newtimer.begin
127                                                 event_end = self.newtimer.end
128                                         else:
129                                                 event_begin = self.timerlist[event[1]].begin
130                                                 event_end = self.timerlist[event[1]].end
131                                         new_event_begin = event[0] + offset_0 + (cnt * 604800)
132                                         # summertime correction
133                                         new_lth = localtime(new_event_begin).tm_hour
134                                         new_event_begin += 3600 * (localtime(event_begin).tm_hour - new_lth)
135                                         new_event_end = new_event_begin + (event_end - event_begin)
136                                         if event[1] == -1:
137                                                 if new_event_begin >= self.newtimer.begin: # is the soap already running?
138                                                         self.nrep_eventlist.extend([(new_event_begin, self.bflag, event[1]),(new_event_end, self.eflag, event[1])])
139                                         else:
140                                                 if new_event_begin >= self.timerlist[event[1]].begin: # is the soap already running?
141                                                         self.nrep_eventlist.extend([(new_event_begin, self.bflag, event[1]),(new_event_end, self.eflag, event[1])])
142                 else:
143                         offset_0 = 345600 # the Epoch begins on Thursday
144                         for cnt in (0, 1): # test two weeks to take care of Sunday-Monday transitions
145                                 for event in self.rep_eventlist:
146                                         if event[1] == -1: # -1 is the identifier of the changed timer
147                                                 event_begin = self.newtimer.begin
148                                                 event_end = self.newtimer.end
149                                         else:
150                                                 event_begin = self.timerlist[event[1]].begin
151                                                 event_end = self.timerlist[event[1]].end
152                                         new_event_begin = event[0] + offset_0 + (cnt * 604800)
153                                         new_event_end = new_event_begin + (event_end - event_begin)
154                                         self.nrep_eventlist.extend([(new_event_begin, self.bflag, event[1]),(new_event_end, self.eflag, event[1])])
155
156 ################################################################################
157 # order list chronological
158                 self.nrep_eventlist.sort()
159
160 ##################################################################################
161 # detect overlapping timers and overlapping times
162                 fakeRecList = []
163                 ConflictTimer = None
164                 ConflictTunerType = None
165                 newTimerTunerType = None
166                 cnt = 0
167                 idx = 0
168                 overlaplist = []
169                 for event in self.nrep_eventlist:
170                         cnt += event[1]
171                         if event[2] == -1: # new timer
172                                 timer = self.newtimer
173                         else:
174                                 timer = self.timerlist[event[2]]
175                         if event[1] == self.bflag:
176                                 tunerType = [ ]
177                                 if timer.service_ref.ref and timer.service_ref.ref.flags & eServiceReference.isGroup:
178                                         fakeRecService = NavigationInstance.instance.recordService(getBestPlayableServiceReference(timer.service_ref.ref, eServiceReference(), True), True)
179                                 else:
180                                         fakeRecService = NavigationInstance.instance.recordService(timer.service_ref, True)
181                                 if fakeRecService:
182                                         fakeRecResult = fakeRecService.start(True)
183                                 else:
184                                         fakeRecResult = -1
185                                 if not fakeRecResult: # tune okay
186                                         if hasattr(fakeRecService, 'frontendInfo') and hasattr(fakeRecService.frontendInfo(), 'getFrontendData'):
187                                                 feinfo = fakeRecService.frontendInfo().getFrontendData()
188                                                 tunerType.append(feinfo.get("tuner_type"))
189                                 else: # tune failed.. so we must go another way to get service type (DVB-S, DVB-T, DVB-C)
190
191                                         def getServiceType(ref): # helper function to get a service type of a service reference
192                                                 serviceInfo = serviceHandler.info(ref)
193                                                 serviceInfo = serviceInfo and serviceInfo.getInfoObject(ref, iServiceInformation.sTransponderData)
194                                                 return serviceInfo and serviceInfo["tuner_type"] or ""
195
196                                         ref = timer.service_ref.ref
197                                         if ref.flags & eServiceReference.isGroup: # service group ?
198                                                 serviceList = serviceHandler.list(ref) # get all alternative services
199                                                 if serviceList:
200                                                         for ref in serviceList.getContent("R"): # iterate over all group service references
201                                                                 type = getServiceType(ref)
202                                                                 if not type in tunerType: # just add single time
203                                                                         tunerType.append(type)
204                                         else:
205                                                 tunerType.append(getServiceType(ref))
206
207                                 if event[2] == -1: # new timer
208                                         newTimerTunerType = tunerType
209                                 overlaplist.append((fakeRecResult, timer, tunerType))
210                                 fakeRecList.append((timer, fakeRecService))
211                                 if fakeRecResult:
212                                         if ConflictTimer is None: # just take care of the first conflict
213                                                 ConflictTimer = timer
214                                                 ConflictTunerType = tunerType
215                         elif event[1] == self.eflag:
216                                 for fakeRec in fakeRecList:
217                                         if timer == fakeRec[0] and fakeRec[1]:
218                                                 NavigationInstance.instance.stopRecordService(fakeRec[1])
219                                                 fakeRecList.remove(fakeRec)
220                                 fakeRec = None
221                                 for entry in overlaplist:
222                                         if entry[1] == timer:
223                                                 overlaplist.remove(entry)
224                         else:
225                                 print "Bug: unknown flag!"
226                         self.nrep_eventlist[idx] = (event[0],event[1],event[2],cnt,overlaplist[:]) # insert a duplicate into current overlaplist
227                         idx += 1
228
229                 if ConflictTimer is None: # no conflict found :)
230                         return True
231
232 ##################################################################################
233 # we have detected a conflict, now we must figure out the involved timers
234
235                 if self.newtimer is not None: # new timer?
236                         if self.newtimer is not ConflictTimer: # the new timer is not the conflicting timer?
237                                 for event in self.nrep_eventlist:
238                                         if len(event[4]) > 1: # entry in overlaplist of this event??
239                                                 kt = False
240                                                 nt = False
241                                                 for entry in event[4]:
242                                                         if entry[1] is ConflictTimer:
243                                                                 kt = True
244                                                         if entry[1] is self.newtimer:
245                                                                 nt = True
246                                                 if nt and kt:
247                                                         ConflictTimer = self.newtimer
248                                                         ConflictTunerType = newTimerTunerType
249                                                         break
250
251                 self.simultimer = [ ConflictTimer ]
252                 for event in self.nrep_eventlist:
253                         if len(event[4]) > 1: # entry in overlaplist of this event??
254                                 for entry in event[4]:
255                                         if entry[1] is ConflictTimer:
256                                                 break
257                                 else:
258                                         continue
259                                 for entry in event[4]:
260                                         if not entry[1] in self.simultimer:
261                                                 for x in entry[2]:
262                                                         if x in ConflictTunerType:
263                                                                 self.simultimer.append(entry[1])
264                                                                 break
265
266                 if len(self.simultimer) < 2:
267                         print "Possible Bug: unknown Conflict!"
268                         return True
269
270                 return False # conflict detected!