110 return "%s; %s" % (info, new) |
110 return "%s; %s" % (info, new) |
111 else: |
111 else: |
112 return new |
112 return new |
113 |
113 |
114 info=None |
114 info=None |
115 this_need_reconstruct=None |
115 this_refresh_time=None |
116 |
116 |
117 if not status.errors.ok(): |
117 if not status.errors.ok(): |
118 info=add_info(info, str(status.errors)) |
118 info=add_info(info, str(status.errors)) |
119 |
119 |
120 if status.state==backup.State.PAUSED: |
120 if status.state==backup.State.PAUSED: |
133 tendtoday=twhen.replace(hour=23,minute=59,second=59) |
133 tendtoday=twhen.replace(hour=23,minute=59,second=59) |
134 tendtomorrow=tendtoday+datetime.timedelta(days=1) |
134 tendtomorrow=tendtoday+datetime.timedelta(days=1) |
135 diff=datetime.timedelta(seconds=when-now) |
135 diff=datetime.timedelta(seconds=when-now) |
136 |
136 |
137 if twhen>tendtomorrow: |
137 if twhen>tendtomorrow: |
|
138 # Display date if scheduled event is after tomorrow |
138 whenday=datetime.date.fromtimestamp(when) |
139 whenday=datetime.date.fromtimestamp(when) |
139 whenstr='on %s' % twhen.date().isoformat() |
140 whenstr='on %s' % twhen.date().isoformat() |
140 this_need_reconstruct=tendtoday+datetime.timedelta(seconds=1) |
141 this_refresh_time=tendtoday+datetime.timedelta(seconds=1) |
141 elif diff.seconds>=12*60*60: # 12 hours |
142 elif twhen>tendtoday and diff.seconds>=12*60*60: # 12 hours |
|
143 # Display 'tomorrow' if the scheduled event is tomorrow and |
|
144 # not earlier than after 12 hours |
142 whenstr='tomorrow' |
145 whenstr='tomorrow' |
143 this_need_reconstruct=twhen-datetime.timedelta(hours=12) |
146 this_refresh_time=twhen-datetime.timedelta(hours=12) |
144 else: |
147 else: |
|
148 # Otherwise, display time |
145 twhen=time.localtime(when) |
149 twhen=time.localtime(when) |
146 if twhen.tm_sec>30: |
150 if twhen.tm_sec>30: |
147 # Round up minute display to avoid user confusion |
151 # Round up minute display to avoid user confusion |
148 twhen=time.localtime(when+30) |
152 twhen=time.localtime(when+30) |
149 whenstr='at %02d:%02d' % (twhen.tm_hour, twhen.tm_min) |
153 whenstr='at %02d:%02d' % (twhen.tm_hour, twhen.tm_min) |
182 if info: |
186 if info: |
183 title=status.name + ' (' + info + ')' |
187 title=status.name + ' (' + info + ')' |
184 else: |
188 else: |
185 title=status.name |
189 title=status.name |
186 |
190 |
187 return title, (status.state, status.errors), this_need_reconstruct |
191 return title, (status.state, status.errors), this_refresh_time |
188 |
192 |
189 class BorgendTray(rumps.App): |
193 class BorgendTray(rumps.App): |
190 def __init__(self, backups): |
194 def __init__(self, backups): |
191 self.lock=Lock() |
195 self.lock=Lock() |
192 self.backups=backups |
196 self.backups=backups |
211 dreamtime.add_callback(self, self.__sleepwake_callback) |
215 dreamtime.add_callback(self, self.__sleepwake_callback) |
212 |
216 |
213 def __rebuild_menu(self): |
217 def __rebuild_menu(self): |
214 menu=[] |
218 menu=[] |
215 state=(backup.State.INACTIVE, backup.Errors.OK) |
219 state=(backup.State.INACTIVE, backup.Errors.OK) |
216 need_reconstruct=None |
220 refresh_time=None |
217 all_paused=True |
221 all_paused=True |
218 |
222 |
219 for index in range(len(self.backups)): |
223 for index in range(len(self.backups)): |
220 b=self.backups[index] |
224 b=self.backups[index] |
221 title, this_state, this_need_reconstruct=make_title(self.statuses[index]) |
225 title, this_state, this_refresh_time=make_title(self.statuses[index]) |
222 # Python closures suck dog's balls... |
226 # Python closures suck dog's balls... |
223 # first and the last program I write in Python until somebody |
227 # first and the last program I write in Python until somebody |
224 # fixes this brain damage |
228 # fixes this brain damage |
225 cbm=lambda sender, _b=b: self.__menu_select_backup(sender, _b) |
229 cbm=lambda sender, _b=b: self.__menu_select_backup(sender, _b) |
226 item=rumps.MenuItem(title, callback=cbm) |
230 item=rumps.MenuItem(title, callback=cbm) |
232 state=combine_state(state, this_state) |
236 state=combine_state(state, this_state) |
233 |
237 |
234 all_paused=(all_paused and this_state[0]==backup.State.PAUSED) |
238 all_paused=(all_paused and this_state[0]==backup.State.PAUSED) |
235 |
239 |
236 # Do we have to automatically update menu display? |
240 # Do we have to automatically update menu display? |
237 if not need_reconstruct: |
241 if not refresh_time: |
238 need_reconstruct=this_need_reconstruct |
242 refresh_time=this_refresh_time |
239 elif this_need_reconstruct: |
243 elif this_refresh_time: |
240 need_reconstruct=min(need_reconstruct, this_need_reconstruct) |
244 refresh_time=min(refresh_time, this_refresh_time) |
241 |
245 |
242 menu_log=rumps.MenuItem("Show log", callback=lambda _: showlog()) |
246 menu_log=rumps.MenuItem("Show log", callback=lambda _: showlog()) |
243 menu.append(menu_log) |
247 menu.append(menu_log) |
244 |
248 |
245 if all_paused: |
249 if all_paused: |
251 |
255 |
252 if not settings['no_quit_menu_entry']: |
256 if not settings['no_quit_menu_entry']: |
253 menu_quit=rumps.MenuItem("Quit...", callback=lambda _: self.quit()) |
257 menu_quit=rumps.MenuItem("Quit...", callback=lambda _: self.quit()) |
254 menu.append(menu_quit) |
258 menu.append(menu_quit) |
255 |
259 |
256 return menu, state, need_reconstruct |
260 return menu, state, refresh_time |
257 |
261 |
258 def build_menu_and_timer(self): |
262 def build_menu_and_timer(self): |
259 if self.refresh_timer: |
263 if self.refresh_timer: |
260 self.refresh_timer.cancel() |
264 self.refresh_timer.cancel() |
261 self.refresh_timer=None |
265 self.refresh_timer=None |
262 self.refresh_timer_time=None |
266 self.refresh_timer_time=None |
|
267 |
263 logger.debug('Rebuilding menu') |
268 logger.debug('Rebuilding menu') |
264 menu, state, need_reconstruct=self.__rebuild_menu() |
269 menu, state, refresh_time=self.__rebuild_menu() |
265 title=trayname(state) |
270 title=trayname(state) |
266 |
271 |
267 if need_reconstruct: |
272 if refresh_time: |
268 when=time.mktime(need_reconstruct.timetuple()) |
273 # Need to time a refresh due to content display changing, |
|
274 # e.g., 'tomorrow' changing to a more specific hour. |
|
275 when=time.mktime(refresh_time.timetuple()) |
269 delay=when-time.time() |
276 delay=when-time.time() |
270 self.refresh_timer=Timer(delay, self.refresh_ui) |
277 if delay>0: |
271 self.refresh_timer_time=need_reconstruct |
278 logger.debug('Timing menu refresh in %s seconds' % delay) |
272 self.refresh_timer.start() |
279 self.refresh_timer=Timer(delay, self.refresh_ui) |
|
280 self.refresh_timer_time=refresh_time |
|
281 self.refresh_timer.start() |
273 |
282 |
274 return menu, title |
283 return menu, title |
275 |
284 |
276 def refresh_ui(self): |
285 def refresh_ui(self): |
277 with self.lock: |
286 with self.lock: |
285 with self.lock: |
294 with self.lock: |
286 self.statuses[index]=status |
295 self.statuses[index]=status |
287 # Time the refresh if it has not been timed, or if the timer |
296 # Time the refresh if it has not been timed, or if the timer |
288 # is timing for the "long-term" (refresh_timer_time set) |
297 # is timing for the "long-term" (refresh_timer_time set) |
289 if not self.refresh_timer or self.refresh_timer_time: |
298 if not self.refresh_timer or self.refresh_timer_time: |
290 logger.debug("Timing refresh") |
299 # logger.debug("Timing refresh") |
291 self.refresh_timer=Timer(refresh_interval, self.refresh_ui) |
300 self.refresh_timer=Timer(refresh_interval, self.refresh_ui) |
292 # refresh_timer_time is only set for "long-term timers" |
301 # refresh_timer_time is only set for "long-term timers" |
293 self.refresh_timer_time=None |
302 self.refresh_timer_time=None |
294 self.refresh_timer.start() |
303 self.refresh_timer.start() |
295 |
304 |