@@ -46,49 +46,18 @@ class hoho(Scheduled):
46
46
47
47
"""
48
48
49
- def update_appointments (self ,when ):
50
- """
51
- # make a flat list from values where comma separated on a single or multiple lines.
52
-
53
- set self.appointments to a list of when something needs to be run during the current day.
54
- """
55
- self .appointments = []
56
- if self .o .scheduled_minute or self .o .scheduled_hour :
57
- for h in self .hours :
58
- for m in self .minutes :
59
- if ( h > when .hour ) or ((h == when .hour ) and ( m >= when .minute )):
60
- appointment = datetime .time (h , m , tzinfo = datetime .timezone .utc )
61
- next_time = datetime .datetime .combine (when ,appointment )
62
- self .appointments .append (next_time )
63
- else :
64
- pass # that time is passed for today.
65
- if self .o .scheduled_time :
66
- for time in self .sched_times :
67
- hour ,minute = time .split (':' )
68
- hour = int (hour )
69
- minute = int (minute )
70
- if ( hour > when .hour ) or ((hour == when .hour ) and ( minute >= when .minute )):
71
- appointment = datetime .time (hour , minute , tzinfo = datetime .timezone .utc )
72
- next_time = datetime .datetime .combine (when ,appointment )
73
- self .appointments .append (next_time )
74
- else :
75
- pass # that time is passed for today.
49
+ def __init__ (self ,options ,class_logger = logger ):
50
+ super ().__init__ (options ,class_logger )
51
+ # if logLevel is set for a subclass, apply it here too
52
+ if hasattr (self .o ,'logLevel' ) and logger :
53
+ logger .setLevel (getattr (logging , self .o .logLevel .upper ()))
76
54
77
- self .appointments .sort ()
78
-
79
-
80
- logger .info ( f"for { when } : { json .dumps (list (map ( lambda x : str (x ), self .appointments ))) } " )
81
-
82
-
83
- def __init__ (self ,options ,logger = logger ):
84
- super ().__init__ (options ,logger )
85
55
self .o .add_option ( 'scheduled_interval' , 'duration' , 0 )
86
56
self .o .add_option ( 'scheduled_hour' , 'list' , [] )
87
57
self .o .add_option ( 'scheduled_minute' , 'list' , [] )
88
58
self .o .add_option ( 'scheduled_time' , 'list' , [] )
89
59
90
60
self .housekeeping_needed = False
91
- self .interrupted = None
92
61
93
62
self .sched_times = sum ([ x .split (',' ) for x in self .o .scheduled_time ],[])
94
63
#self.sched_times.sort()
@@ -104,7 +73,6 @@ def __init__(self,options,logger=logger):
104
73
self .minutes = list (map ( lambda x : int (x ), sched_min ))
105
74
#self.minutes.sort()
106
75
107
-
108
76
self .default_wait = datetime .timedelta (seconds = 300 )
109
77
110
78
logger .debug ( f'minutes: { self .minutes } ' )
@@ -114,104 +82,69 @@ def __init__(self,options,logger=logger):
114
82
self .first_interval = True
115
83
116
84
if self .o .scheduled_interval <= 0 and not self .appointments :
117
- logger .info ( f"no scheduled_interval or appointments (combination of scheduled_hour and scheduled_minute) set defaulting to every { self .default_wait .seconds } seconds" )
85
+ logger .warning ( f"no scheduled_interval or appointments (combination of scheduled_hour and scheduled_minute) set defaulting to every { self .default_wait .seconds } seconds" )
118
86
119
- def gather ( self , messageCountMax ):
120
-
121
- # for next expected post
122
- self .wait_until_next ()
123
-
124
- if self . stop_requested or self .housekeeping_needed :
125
- return ( False , [] )
87
+ # Determine the next gather time
88
+ # For scheduled_interval, gather immediately after starting
89
+ if self . o . scheduled_interval and self . o . scheduled_interval > 0 :
90
+ self .next_gather_time = now
91
+ else :
92
+ self .next_gather_time = None
93
+ self . calc_next_gather_time ( )
126
94
127
- logger . info ( 'time to run' )
95
+ self . last_gather_time = 0
128
96
129
- # always post the same file at different time
130
- gathered_messages = []
131
-
132
- for relPath in self .o .path :
133
- st = FmdStat ()
134
- m = sarracenia .Message .fromFileInfo (relPath , self .o , st )
135
- gathered_messages .append (m )
136
-
137
- return (True , gathered_messages )
138
-
139
- def on_housekeeping (self ):
140
-
141
- self .housekeeping_needed = False
142
-
143
-
144
- def wait_seconds (self ,sleepfor ):
145
- """
146
- sleep for the given number of seconds, like time.sleep() but broken into
147
- shorter naps to be able to honour stop_requested, or when housekeeping is needed.
148
-
97
+ def update_appointments (self ,when ):
149
98
"""
99
+ # make a flat list from values where comma separated on a single or multiple lines.
150
100
151
- housekeeping = datetime .timedelta (seconds = self .o .housekeeping )
152
- nap = datetime .timedelta (seconds = 10 )
153
-
154
- if self .interrupted :
155
- sleepfor = self .interrupted
156
- now = datetime .datetime .fromtimestamp (time .time (),datetime .timezone .utc )
157
-
158
- # update sleep remaining based on how long other processing took.
159
- interruption_duration = now - self .interrupted_when
160
- sleepfor -= interruption_duration
161
-
162
- if sleepfor < nap :
163
- nap = sleepfor
164
-
165
- sleptfor = datetime .timedelta (seconds = 0 )
166
-
167
- while sleepfor > datetime .timedelta (seconds = 0 ):
168
- time .sleep (nap .total_seconds ())
169
- if self .stop_requested :
170
- return
171
-
172
- # how long is left to sleep.
173
- sleepfor -= nap
174
- self .interrupted = sleepfor
175
- self .interrupted_when = datetime .datetime .fromtimestamp (time .time (),datetime .timezone .utc )
176
-
177
- sleptfor += nap
178
- if sleptfor > housekeeping :
179
- self .housekeeping_needed = True
180
- return
181
-
182
- # got to the end of the interval...
183
- self .interrupted = None
184
-
185
- def wait_until ( self , appointment ):
186
-
187
- now = datetime .datetime .fromtimestamp (time .time (),datetime .timezone .utc )
188
-
189
- sleepfor = appointment - now
190
-
191
- logger .info ( f"appointment at: { appointment } , need to wait: { sleepfor } )" )
192
- self .wait_seconds ( sleepfor )
193
-
194
-
195
- def wait_until_next ( self ):
196
-
197
- one_min = datetime .timedelta (minutes = 1 )
198
-
199
- if self .o .scheduled_interval > 0 :
200
- if self .first_interval :
201
- self .first_interval = False
202
- return
101
+ set self.appointments to a list of when something needs to be run during the current day.
102
+ """
103
+ self .appointments = []
104
+ if self .o .scheduled_minute or self .o .scheduled_hour :
105
+ for h in self .hours :
106
+ for m in self .minutes :
107
+ if ( h > when .hour ) or ((h == when .hour ) and ( m >= when .minute )):
108
+ appointment = datetime .time (h , m , tzinfo = datetime .timezone .utc )
109
+ next_time = datetime .datetime .combine (when ,appointment )
110
+ self .appointments .append (next_time )
111
+ else :
112
+ pass # that time is passed for today.
113
+ if self .o .scheduled_time :
114
+ for time in self .sched_times :
115
+ hour ,minute = time .split (':' )
116
+ hour = int (hour )
117
+ minute = int (minute )
118
+ if ( hour > when .hour ) or ((hour == when .hour ) and ( minute >= when .minute )):
119
+ appointment = datetime .time (hour , minute , tzinfo = datetime .timezone .utc )
120
+ next_time = datetime .datetime .combine (when ,appointment )
121
+ self .appointments .append (next_time )
122
+ else :
123
+ pass # that time is passed for today.
124
+
125
+ self .appointments .sort ()
126
+ logger .info ( f"for { when } : { self .appointments_to_string ()} " )
203
127
204
- self . wait_seconds ( datetime . timedelta ( seconds = self . o . scheduled_interval ))
205
- return
128
+ def appointments_to_string ( self ):
129
+ return json . dumps ( list ( map ( lambda x : str ( x ), self . appointments )))
206
130
207
- if ( len (self .o .scheduled_hour ) > 0 ) or ( len (self .o .scheduled_minute ) > 0 ) or self .o .scheduled_time :
208
- now = datetime .datetime .fromtimestamp (time .time (),datetime .timezone .utc )
131
+ def calc_next_gather_time (self , last_gather = 0 ):
132
+ if self .next_gather_time in self .appointments :
133
+ self .appointments .remove (self .next_gather_time )
134
+ if last_gather == 0 :
135
+ last_gather = datetime .datetime .now (datetime .timezone .utc )
136
+
137
+ # Scheduled interval overrides other options
138
+ if self .o .scheduled_interval and self .o .scheduled_interval > 0 :
139
+ self .next_gather_time = last_gather + datetime .timedelta (seconds = self .o .scheduled_interval )
140
+ logger .debug (f"next gather should be in { self .o .scheduled_interval } s, scheduled for { self .next_gather_time } " )
141
+
142
+ # No scheduled interval --> try to use configured schedule
143
+ elif len (self .o .scheduled_hour ) > 0 or len (self .o .scheduled_minute ) > 0 or len (self .o .scheduled_time ) > 0 :
209
144
next_appointment = None
210
145
missed_appointments = []
211
146
for t in self .appointments :
212
- # Need a little bit before or after apointment, to allow some wiggle room for sleep overhead.
213
- # See issue #1214 for more details
214
- if t < now < t + one_min :
147
+ if last_gather < t :
215
148
next_appointment = t
216
149
break
217
150
else :
@@ -230,21 +163,47 @@ def wait_until_next( self ):
230
163
self .update_appointments (midnight )
231
164
next_appointment = self .appointments [0 ]
232
165
233
- self .wait_until (next_appointment )
234
- if self .interrupted :
235
- logger .info ( f"sleep interrupted, returning for housekeeping." )
236
- else :
237
- self .appointments .remove (next_appointment )
238
- logger .info ( f"ok { len (self .appointments )} appointments left today" )
239
- return
240
-
241
- # default wait...
242
-
243
- if self .first_interval :
244
- self .first_interval = False
245
- return
166
+ self .next_gather_time = next_appointment
167
+ logger .debug (f"next gather scheduled for { self .next_gather_time } from appointments { self .appointments_to_string ()} " )
168
+
169
+ # No scheduled interval and no scheduled hour/minutes/time
170
+ else :
171
+ self .next_gather_time = last_gather + self .default_wait
172
+ logger .debug (f"next gather should be in { self .default_wait .seconds } s, scheduled for { self .next_gather_time } (default_wait" )
173
+
174
+ def ready_to_gather (self ):
175
+ current_time = datetime .datetime .now (datetime .timezone .utc )
176
+ if current_time >= self .next_gather_time and not self .stop_requested :
177
+ late = (current_time - self .next_gather_time ).total_seconds ()
178
+ logger .info (f"--> yes, now >= { self .next_gather_time } ({ late } s late)" )
179
+ # NOTE: could also pass self.next_gather_time to calc_next_gather_time to get more precise intervals
180
+ # See https://github.com/MetPX/sarracenia/issues/1214#issuecomment-2344711046 for discussion.
181
+ self .calc_next_gather_time (current_time )
182
+ self .last_gather_time = current_time
183
+ return True
184
+ else :
185
+ logger .debug (f"--> no, next gather scheduled for { self .next_gather_time } " )
186
+
187
+ def gather (self , messageCountMax ):
188
+ if self .ready_to_gather ():
189
+ # always post the same file at different time
190
+ gathered_messages = []
191
+ for relPath in self .o .path :
192
+ st = FmdStat ()
193
+ m = sarracenia .Message .fromFileInfo (relPath , self .o , st )
194
+ gathered_messages .append (m )
195
+ return (True , gathered_messages )
196
+ else :
197
+ logger .debug (f"nothing to do" )
198
+ return (False , [])
246
199
247
- self .wait_seconds (self .default_wait )
200
+ def on_housekeeping (self ):
201
+ logger .info (f"next gather scheduled for { self .next_gather_time } " )
202
+ n_appointments = len (self .appointments )
203
+ if n_appointments > 0 :
204
+ logger .info (f"{ n_appointments } appointments remaining for today" )
205
+ logger .debug (f"remaining appointments: { self .appointments_to_string ()} " )
206
+ self .housekeeping_needed = False
248
207
249
208
if __name__ == '__main__' :
250
209
0 commit comments