81 return "%s; %s" % (info, new) |
81 return "%s; %s" % (info, new) |
82 else: |
82 else: |
83 return new |
83 return new |
84 |
84 |
85 info=None |
85 info=None |
|
86 this_need_reconstruct=None |
86 |
87 |
87 if not status.errors.ok(): |
88 if not status.errors.ok(): |
88 info=add_info(info, str(status.errors)) |
89 info=add_info(info, str(status.errors)) |
89 |
90 |
90 if status.state==backup.State.SCHEDULED: |
91 if status.state==backup.State.SCHEDULED: |
91 # Operation scheduled |
92 # Operation scheduled |
92 when=status.when() |
93 when=status.when() |
93 now=time.time() |
94 now=time.time() |
|
95 |
94 if when<now: |
96 if when<now: |
95 whenstr='overdue' |
97 whenstr='overdue' |
96 info='' |
98 info='' |
97 else: |
99 else: |
|
100 tnow=datetime.datetime.fromtimestamp(now) |
|
101 twhen=datetime.datetime.fromtimestamp(when) |
|
102 tendtoday=twhen.replace(hour=23,minute=59,second=59) |
|
103 tendtomorrow=tendtoday+datetime.timedelta(days=1) |
98 diff=datetime.timedelta(seconds=when-now) |
104 diff=datetime.timedelta(seconds=when-now) |
99 if diff.days>0: |
105 |
|
106 if twhen>tendtomorrow: |
100 whenday=datetime.date.fromtimestamp(when) |
107 whenday=datetime.date.fromtimestamp(when) |
101 whenstr='on %s' % whenday.isoformat() |
108 whenstr='on %s' % twhen.date().isoformat() |
|
109 this_need_reconstruct=tendtoday+datetime.timedelta(seconds=1) |
|
110 elif diff.seconds>=12*60*60: # 12 hours |
|
111 whenstr='tomorrow' |
|
112 this_need_reconstruct=twhen-datetime.timedelta(hours=12) |
102 else: |
113 else: |
103 twhen=time.localtime(when) |
114 twhen=time.localtime(when) |
104 if twhen.tm_sec>30: |
115 if twhen.tm_sec>30: |
105 # Round up minute display to avoid user confusion |
116 # Round up minute display to avoid user confusion |
106 twhen=time.localtime(when+30) |
117 twhen=time.localtime(when+30) |
159 cb=(lambda status, errors=None, _index=index: |
170 cb=(lambda status, errors=None, _index=index: |
160 self.__status_callback(_index, status, errorlog=errors)) |
171 self.__status_callback(_index, status, errorlog=errors)) |
161 b.set_status_update_callback(cb) |
172 b.set_status_update_callback(cb) |
162 self.statuses[index]=b.status() |
173 self.statuses[index]=b.status() |
163 |
174 |
164 menu, state=self.__rebuild_menu() |
|
165 |
|
166 self.refresh_timer=None |
175 self.refresh_timer=None |
167 |
176 self.refresh_timer_time=None |
168 super().__init__(trayname(state), menu=menu, quit_button=None) |
177 |
|
178 menu, title=self.build_menu_and_timer() |
|
179 |
|
180 super().__init__(title, menu=menu, quit_button=None) |
|
181 |
169 |
182 |
170 def __rebuild_menu(self): |
183 def __rebuild_menu(self): |
171 menu=[] |
184 menu=[] |
172 state=(backup.State.INACTIVE, backup.Errors.OK) |
185 state=(backup.State.INACTIVE, backup.Errors.OK) |
|
186 need_reconstruct=None |
173 for index in range(len(self.backups)): |
187 for index in range(len(self.backups)): |
174 b=self.backups[index] |
188 b=self.backups[index] |
175 title, this_state=make_title(self.statuses[index]) |
189 title, this_state, this_need_reconstruct=make_title(self.statuses[index]) |
176 # Python closures suck dog's balls... |
190 # Python closures suck dog's balls... |
177 # first and the last program I write in Python until somebody |
191 # first and the last program I write in Python until somebody |
178 # fixes this brain damage |
192 # fixes this brain damage |
179 cbm=lambda sender, _b=b: self.__menu_select_backup(sender, _b) |
193 cbm=lambda sender, _b=b: self.__menu_select_backup(sender, _b) |
180 item=rumps.MenuItem(title, callback=cbm) |
194 item=rumps.MenuItem(title, callback=cbm) |
183 elif this_state[0]==backup.State.SCHEDULED or this_state[0]==backup.State.QUEUED: |
197 elif this_state[0]==backup.State.SCHEDULED or this_state[0]==backup.State.QUEUED: |
184 item.state=1 |
198 item.state=1 |
185 menu.append(item) |
199 menu.append(item) |
186 state=combine_state(state, this_state) |
200 state=combine_state(state, this_state) |
187 |
201 |
|
202 # Do we have to automatically update menu display? |
|
203 if not need_reconstruct: |
|
204 need_reconstruct=this_need_reconstruct |
|
205 elif this_need_reconstruct: |
|
206 need_reconstruct=min(need_reconstruct, this_need_reconstruct) |
|
207 |
188 menu_log=rumps.MenuItem("Show log", callback=lambda _: showlog()) |
208 menu_log=rumps.MenuItem("Show log", callback=lambda _: showlog()) |
189 menu.append(menu_log) |
209 menu.append(menu_log) |
190 |
210 |
191 if not settings['no_quit_menu_entry']: |
211 if not settings['no_quit_menu_entry']: |
192 menu_quit=rumps.MenuItem("Quit...", callback=lambda _: self.quit()) |
212 menu_quit=rumps.MenuItem("Quit...", callback=lambda _: self.quit()) |
193 menu.append(menu_quit) |
213 menu.append(menu_quit) |
194 |
214 |
195 return menu, state |
215 return menu, state, need_reconstruct |
|
216 |
|
217 def build_menu_and_timer(self): |
|
218 if self.refresh_timer: |
|
219 self.refresh_timer.cancel() |
|
220 self.refresh_timer=None |
|
221 self.refresh_timer_time=None |
|
222 logger.debug('Rebuilding menu') |
|
223 menu, state, need_reconstruct=self.__rebuild_menu() |
|
224 title=trayname(state) |
|
225 |
|
226 if need_reconstruct: |
|
227 when=time.mktime(need_reconstruct.timetuple()) |
|
228 delay=when-time.time() |
|
229 self.refresh_timer=Timer(delay, self.refresh_ui) |
|
230 self.refresh_timer_time=need_reconstruct |
|
231 self.refresh_timer.start() |
|
232 |
|
233 return menu, title |
196 |
234 |
197 def refresh_ui(self): |
235 def refresh_ui(self): |
198 with self.lock: |
236 with self.lock: |
199 self.refresh_timer=None |
237 menu, title=self.build_menu_and_timer() |
200 logger.debug('Rebuilding menu') |
|
201 menu, state=self.__rebuild_menu() |
|
202 self.menu.clear() |
238 self.menu.clear() |
203 self.menu.update(menu) |
239 self.menu.update(menu) |
204 self.title=trayname(state) |
240 self.title=title |
205 |
241 |
206 def __status_callback(self, index, status, errorlog=None): |
242 def __status_callback(self, index, status, errorlog=None): |
207 logger.debug('Status callback: %s' % str(status)) |
243 logger.debug('Status callback: %s' % str(status)) |
208 |
244 |
209 with self.lock: |
245 with self.lock: |
210 self.statuses[index]=status |
246 self.statuses[index]=status |
211 if self.refresh_timer==None: |
247 if self.refresh_timer==None and not self.refresh_timer_time: |
212 self.refresh_timer=Timer(refresh_interval, self.refresh_ui) |
248 self.refresh_timer=Timer(refresh_interval, self.refresh_ui) |
|
249 # refresh_timer_time is only set for "long-term timers" |
|
250 self.refresh_timer_time=None |
213 self.refresh_timer.start() |
251 self.refresh_timer.start() |
214 |
252 |
215 if errorlog: |
253 if errorlog: |
216 if 'msgid' not in errorlog or not isinstance(errorlog['msgid'], str): |
254 if 'msgid' not in errorlog or not isinstance(errorlog['msgid'], str): |
217 msgid='UnknownError' |
255 msgid='UnknownError' |