cleanup some imports
[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.resetRepeated()
18                 #begindate = localtime(self.begin)
19                 #newdate = datetime.datetime(begindate.tm_year, begindate.tm_mon, begindate.tm_mday 0, 0, 0);
20                 self.repeatedbegindate = begin
21                 self.backoff = 0
22                 
23                 self.disabled = False
24                 
25         def resetRepeated(self):
26                 self.repeated = int(0)
27
28         def setRepeated(self, day):
29                 self.repeated |= (2 ** day)
30                 print "Repeated: " + str(self.repeated)
31                 
32         def isRunning(self):
33                 return self.state == self.StateRunning
34                 
35         def addOneDay(self, timedatestruct):
36                 oldHour = timedatestruct.tm_hour
37                 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()
38                 if localtime(mktime(newdate)).tm_hour != oldHour:
39                         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()                        
40                 return newdate
41                 
42         # update self.begin and self.end according to the self.repeated-flags
43         def processRepeated(self, findRunningEvent = True):
44                 print "ProcessRepeated"
45                 if (self.repeated != 0):
46                         now = int(time()) + 1
47
48                         #to avoid problems with daylight saving, we need to calculate with localtime, in struct_time representation
49                         localrepeatedbegindate = localtime(self.repeatedbegindate)
50                         localbegin = localtime(self.begin)
51                         localend = localtime(self.end)
52                         localnow = localtime(now)
53
54                         print "localbegin:", strftime("%c", localbegin)
55                         print "localend:", strftime("%c", localend)
56
57                         day = []
58                         flags = self.repeated
59                         for x in range(0, 7):
60                                 if (flags & 1 == 1):
61                                         day.append(0)
62                                         print "Day: " + str(x)
63                                 else:
64                                         day.append(1)
65                                 flags = flags >> 1
66
67                         print strftime("%c", localnow)
68
69                         # if day is NOT in the list of repeated days
70                         # 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
71                         while ((day[localbegin.tm_wday] != 0) or (mktime(localrepeatedbegindate) > mktime(localbegin))  or ((day[localbegin.tm_wday] == 0) and ((findRunningEvent and localend < localnow) or ((not findRunningEvent) and localbegin < localnow)))):
72                                 localbegin = self.addOneDay(localbegin)
73                                 localend = self.addOneDay(localend)
74                                 print "localbegin after addOneDay:", strftime("%c", localbegin)
75                                 print "localend after addOneDay:", strftime("%c", localend)
76                                 
77                         #we now have a struct_time representation of begin and end in localtime, but we have to calculate back to (gmt) seconds since epoch
78                         self.begin = int(mktime(localbegin))
79                         self.end = int(mktime(localend))
80                         if self.begin == self.end:
81                                 self.end += 1
82
83                         print "ProcessRepeated result"
84                         print strftime("%c", localtime(self.begin))
85                         print strftime("%c", localtime(self.end))
86
87                         self.timeChanged()
88
89         def __lt__(self, o):
90                 return self.getNextActivation() < o.getNextActivation()
91         
92         # must be overridden
93         def activate(self):
94                 pass
95                 
96         # can be overridden
97         def timeChanged(self):
98                 pass
99
100         # check if a timer entry must be skipped
101         def shouldSkip(self):
102                 return self.end <= time() and self.state == TimerEntry.StateWaiting
103
104         def abort(self):
105                 self.end = time()
106                 
107                 # in case timer has not yet started, but gets aborted (so it's preparing),
108                 # set begin to now.
109                 if self.begin > self.end:
110                         self.begin = self.end
111
112                 self.cancelled = True
113         
114         # must be overridden!
115         def getNextActivation():
116                 pass
117
118         def disable(self):
119                 self.disabled = True
120         
121         def enable(self):
122                 self.disabled = False
123
124 class Timer:
125         # the time between "polls". We do this because
126         # we want to account for time jumps etc.
127         # of course if they occur <100s before starting,
128         # it's not good. thus, you have to repoll when
129         # you change the time.
130         #
131         # this is just in case. We don't want the timer 
132         # hanging. we use this "edge-triggered-polling-scheme"
133         # anyway, so why don't make it a bit more fool-proof?
134         MaxWaitTime = 100
135
136         def __init__(self):
137                 self.timer_list = [ ]
138                 self.processed_timers = [ ]
139                 
140                 self.timer = eTimer()
141                 self.timer.timeout.get().append(self.calcNextActivation)
142                 self.lastActivation = time()
143                 
144                 self.calcNextActivation()
145                 self.on_state_change = [ ]
146
147         def stateChanged(self, entry):
148                 for f in self.on_state_change:
149                         f(entry)
150
151         def cleanup(self):
152                 self.processed_timers = [entry for entry in self.processed_timers if entry.disabled]
153         
154         def addTimerEntry(self, entry, noRecalc=0):
155                 entry.processRepeated()
156
157                 # when the timer has not yet started, and is already passed,
158                 # don't go trough waiting/running/end-states, but sort it
159                 # right into the processedTimers.
160                 if entry.shouldSkip() or entry.state == TimerEntry.StateEnded or (entry.state == TimerEntry.StateWaiting and entry.disabled):
161                         print "already passed, skipping"
162                         print "shouldSkip:", entry.shouldSkip()
163                         print "state == ended", entry.state == TimerEntry.StateEnded
164                         print "waiting && disabled:", (entry.state == TimerEntry.StateWaiting and entry.disabled)
165                         insort(self.processed_timers, entry)
166                         entry.state = TimerEntry.StateEnded
167                 else:
168                         insort(self.timer_list, entry)
169                         if not noRecalc:
170                                 self.calcNextActivation()
171         
172         def setNextActivation(self, when):
173                 delay = int((when - time()) * 1000)
174                 print "[timer.py] next activation: %d (in %d ms)" % (when, delay)
175                 
176                 self.timer.start(delay, 1)
177                 self.next = when
178
179         def calcNextActivation(self):
180                 if self.lastActivation > time():
181                         print "[timer.py] timewarp - re-evaluating all processed timers."
182                         tl = self.processed_timers
183                         self.processed_timers = [ ]
184                         for x in tl:
185                                 # simulate a "waiting" state to give them a chance to re-occure
186                                 x.resetState()
187                                 self.addTimerEntry(x, noRecalc=1)
188                 
189                 self.processActivation()
190                 self.lastActivation = time()
191         
192                 min = int(time()) + self.MaxWaitTime
193                 
194                 # calculate next activation point
195                 if len(self.timer_list):
196                         w = self.timer_list[0].getNextActivation()
197                         if w < min:
198                                 min = w
199                 
200                 self.setNextActivation(min)
201         
202         def timeChanged(self, timer):
203                 print "time changed"
204                 timer.timeChanged()
205                 if timer.state == TimerEntry.StateEnded:
206                         self.processed_timers.remove(timer)
207                 else:
208                         self.timer_list.remove(timer)
209
210                 # give the timer a chance to re-enqueue
211                 if timer.state == TimerEntry.StateEnded:
212                         timer.state = TimerEntry.StateWaiting
213                 self.addTimerEntry(timer)
214         
215         def doActivate(self, w):
216                 self.timer_list.remove(w)
217                 
218                 # when activating a timer which has already passed,
219                 # simply abort the timer. don't run trough all the stages.
220                 if w.shouldSkip():
221                         w.state = TimerEntry.StateEnded
222                 else:
223                         # when active returns true, this means "accepted".
224                         # otherwise, the current state is kept.
225                         # the timer entry itself will fix up the delay then.
226                         if w.activate():
227                                 w.state += 1
228
229                 # did this timer reached the last state?
230                 if w.state < TimerEntry.StateEnded:
231                         # no, sort it into active list
232                         insort(self.timer_list, w)
233                 else:
234                         # yes. Process repeated, and re-add.
235                         if w.repeated:
236                                 w.processRepeated()
237                                 w.state = TimerEntry.StateWaiting
238                                 self.addTimerEntry(w)
239                         else:
240                                 insort(self.processed_timers, w)
241                 
242                 self.stateChanged(w)
243
244         def processActivation(self):
245                 print "It's now ", strftime("%c", localtime(time()))
246                 t = int(time()) + 1
247                 
248                 # we keep on processing the first entry until it goes into the future.
249                 while len(self.timer_list) and self.timer_list[0].getNextActivation() < t:
250                         self.doActivate(self.timer_list[0])