Update Latvian translation
[openblackhole/openblackhole-enigma2.git] / timer.py
1 from bisect import insort
2 from time import strftime, time, localtime, mktime
3 from enigma import eTimer
4 import datetime
5
6 class TimerEntry:
7         StateWaiting  = 0
8         StatePrepared = 1
9         StateRunning  = 2
10         StateEnded    = 3
11
12         def __init__(self, begin, end):
13                 self.begin = begin
14                 self.prepare_time = 20
15                 self.end = end
16                 self.state = 0
17                 self.findRunningEvent = True
18                 self.findNextEvent = False
19                 self.resetRepeated()
20                 #begindate = localtime(self.begin)
21                 #newdate = datetime.datetime(begindate.tm_year, begindate.tm_mon, begindate.tm_mday 0, 0, 0);
22                 self.repeatedbegindate = begin
23                 self.backoff = 0
24
25                 self.disabled = False
26
27         def resetState(self):
28                 self.state = self.StateWaiting
29                 self.cancelled = False
30                 self.first_try_prepare = True
31                 self.findRunningEvent = True
32                 self.findNextEvent = False
33                 self.timeChanged()
34
35         def resetRepeated(self):
36                 self.repeated = int(0)
37
38         def setRepeated(self, day):
39                 self.repeated |= (2 ** day)
40
41         def isRunning(self):
42                 return self.state == self.StateRunning
43
44         def addOneDay(self, timedatestruct):
45                 oldHour = timedatestruct.tm_hour
46                 newdate =  (datetime.datetime(timedatestruct.tm_year, timedatestruct.tm_mon, timedatestruct.tm_mday, timedatestruct.tm_hour, timedatestruct.tm_min, timedatestruct.tm_sec) + datetime.timedelta(days=1)).timetuple()
47                 if localtime(mktime(newdate)).tm_hour != oldHour:
48                         return (datetime.datetime(timedatestruct.tm_year, timedatestruct.tm_mon, timedatestruct.tm_mday, timedatestruct.tm_hour, timedatestruct.tm_min, timedatestruct.tm_sec) + datetime.timedelta(days=2)).timetuple()
49                 return newdate
50
51         def isFindRunningEvent(self):
52                 return self.findRunningEvent
53
54         def isFindNextEvent(self):
55                 return self.findNextEvent
56
57         # update self.begin and self.end according to the self.repeated-flags
58         def processRepeated(self, findRunningEvent=True, findNextEvent=False):
59                 if (self.repeated != 0):
60                         now = int(time()) + 1
61                         if findNextEvent:
62                                 now = self.end + 120
63                         self.findRunningEvent = findRunningEvent
64                         self.findNextEvent = findNextEvent
65                         #to avoid problems with daylight saving, we need to calculate with localtime, in struct_time representation
66                         localrepeatedbegindate = localtime(self.repeatedbegindate)
67                         localbegin = localtime(self.begin)
68                         localend = localtime(self.end)
69                         localnow = localtime(now)
70
71                         day = []
72                         flags = self.repeated
73                         for x in (0, 1, 2, 3, 4, 5, 6):
74                                 if (flags & 1 == 1):
75                                         day.append(0)
76                                 else:
77                                         day.append(1)
78                                 flags = flags >> 1
79
80                         # if day is NOT in the list of repeated days
81                         # OR if the day IS in the list of the repeated days, check, if event is currently running... then if findRunningEvent is false, go to the next event
82                         while ((day[localbegin.tm_wday] != 0) or (mktime(localrepeatedbegindate) > mktime(localbegin))  or
83                                 ((day[localbegin.tm_wday] == 0) and ((findRunningEvent and localend < localnow) or ((not findRunningEvent) and localbegin < localnow)))):
84                                 localbegin = self.addOneDay(localbegin)
85                                 localend = self.addOneDay(localend)
86
87                         #we now have a struct_time representation of begin and end in localtime, but we have to calculate back to (gmt) seconds since epoch
88                         self.begin = int(mktime(localbegin))
89                         self.end = int(mktime(localend))
90                         if self.begin == self.end:
91                                 self.end += 1
92
93                         self.timeChanged()
94
95         def __lt__(self, o):
96                 return self.getNextActivation() < o.getNextActivation()
97
98         # must be overridden
99         def activate(self):
100                 pass
101
102         # can be overridden
103         def timeChanged(self):
104                 pass
105
106         # check if a timer entry must be skipped
107         def shouldSkip(self):
108                 return self.end <= time() and self.state == TimerEntry.StateWaiting
109
110         def abort(self):
111                 self.end = time()
112
113                 # in case timer has not yet started, but gets aborted (so it's preparing),
114                 # set begin to now.
115                 if self.begin > self.end:
116                         self.begin = self.end
117
118                 self.cancelled = True
119
120         # must be overridden!
121         def getNextActivation():
122                 pass
123
124         def disable(self):
125                 self.disabled = True
126
127         def enable(self):
128                 self.disabled = False
129
130 class Timer:
131         # the time between "polls". We do this because
132         # we want to account for time jumps etc.
133         # of course if they occur <100s before starting,
134         # it's not good. thus, you have to repoll when
135         # you change the time.
136         #
137         # this is just in case. We don't want the timer
138         # hanging. we use this "edge-triggered-polling-scheme"
139         # anyway, so why don't make it a bit more fool-proof?
140         MaxWaitTime = 100
141
142         def __init__(self):
143                 self.timer_list = [ ]
144                 self.processed_timers = [ ]
145
146                 self.timer = eTimer()
147                 self.timer.callback.append(self.calcNextActivation)
148                 self.lastActivation = time()
149
150                 self.calcNextActivation()
151                 self.on_state_change = [ ]
152
153         def stateChanged(self, entry):
154                 for f in self.on_state_change:
155                         f(entry)
156
157         def cleanup(self):
158                 self.processed_timers = [entry for entry in self.processed_timers if entry.disabled]
159
160         def cleanupDaily(self, days):
161                 limit = time() - (days * 3600 * 24)
162                 self.processed_timers = [entry for entry in self.processed_timers if (entry.disabled and entry.repeated) or (entry.end and (entry.end > limit))]
163
164         def addTimerEntry(self, entry, noRecalc=0):
165                 entry.processRepeated()
166
167                 # when the timer has not yet started, and is already passed,
168                 # don't go trough waiting/running/end-states, but sort it
169                 # right into the processedTimers.
170                 if entry.shouldSkip() or entry.state == TimerEntry.StateEnded or (entry.state == TimerEntry.StateWaiting and entry.disabled):
171                         insort(self.processed_timers, entry)
172                         entry.state = TimerEntry.StateEnded
173                 else:
174                         insort(self.timer_list, entry)
175                         if not noRecalc:
176                                 self.calcNextActivation()
177
178 # small piece of example code to understand how to use record simulation
179 #               if NavigationInstance.instance:
180 #                       lst = [ ]
181 #                       cnt = 0
182 #                       for timer in self.timer_list:
183 #                               print "timer", cnt
184 #                               cnt += 1
185 #                               if timer.state == 0: #waiting
186 #                                       lst.append(NavigationInstance.instance.recordService(timer.service_ref))
187 #                               else:
188 #                                       print "STATE: ", timer.state
189 #
190 #                       for rec in lst:
191 #                               if rec.start(True): #simulate
192 #                                       print "FAILED!!!!!!!!!!!!"
193 #                               else:
194 #                                       print "OK!!!!!!!!!!!!!!"
195 #                               NavigationInstance.instance.stopRecordService(rec)
196 #               else:
197 #                       print "no NAV"
198
199         def setNextActivation(self, now, when):
200                 delay = int((when - now) * 1000)
201                 self.timer.start(delay, 1)
202                 self.next = when
203
204         def calcNextActivation(self):
205                 now = time()
206                 if self.lastActivation > now:
207                         print "[timer.py] timewarp - re-evaluating all processed timers."
208                         tl = self.processed_timers
209                         self.processed_timers = [ ]
210                         for x in tl:
211                                 # simulate a "waiting" state to give them a chance to re-occure
212                                 x.resetState()
213                                 self.addTimerEntry(x, noRecalc=1)
214
215                 self.processActivation()
216                 self.lastActivation = now
217
218                 min = int(now) + self.MaxWaitTime
219
220                 # calculate next activation point
221                 if self.timer_list:
222                         w = self.timer_list[0].getNextActivation()
223                         if w < min:
224                                 min = w
225
226                 if int(now) < 1072224000 and min > now + 5:
227                         # system time has not yet been set (before 01.01.2004), keep a short poll interval
228                         min = now + 5
229
230                 self.setNextActivation(now, min)
231
232         def timeChanged(self, timer):
233                 print "time changed"
234                 timer.timeChanged()
235                 if timer.state == TimerEntry.StateEnded:
236                         self.processed_timers.remove(timer)
237                 else:
238                         try:
239                                 self.timer_list.remove(timer)
240                         except:
241                                 print "[timer] Failed to remove, not in list"
242                                 return
243                 # give the timer a chance to re-enqueue
244                 if timer.state == TimerEntry.StateEnded:
245                         timer.state = TimerEntry.StateWaiting
246                 self.addTimerEntry(timer)
247
248         def doActivate(self, w):
249                 self.timer_list.remove(w)
250
251                 # when activating a timer which has already passed,
252                 # simply abort the timer. don't run trough all the stages.
253                 if w.shouldSkip():
254                         w.state = TimerEntry.StateEnded
255                 else:
256                         # when active returns true, this means "accepted".
257                         # otherwise, the current state is kept.
258                         # the timer entry itself will fix up the delay then.
259                         if w.activate():
260                                 w.state += 1
261
262                 # did this timer reached the last state?
263                 if w.state < TimerEntry.StateEnded:
264                         # no, sort it into active list
265                         insort(self.timer_list, w)
266                 else:
267                         # yes. Process repeated, and re-add.
268                         if w.repeated:
269                                 w.processRepeated()
270                                 w.state = TimerEntry.StateWaiting
271                                 self.addTimerEntry(w)
272                         else:
273                                 insort(self.processed_timers, w)
274
275                 self.stateChanged(w)
276
277         def processActivation(self):
278                 t = int(time()) + 1
279                 # we keep on processing the first entry until it goes into the future.
280                 while self.timer_list and self.timer_list[0].getNextActivation() < t:
281                         self.doActivate(self.timer_list[0])