ui.py

changeset 72
e0e6043779e2
parent 62
b7d13b2ad67e
child 75
2a44b9649212
equal deleted inserted replaced
71:a8a5ebb64e02 72:e0e6043779e2
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)
135 if info: 146 if info:
136 title=status.name + ' (' + info + ')' 147 title=status.name + ' (' + info + ')'
137 else: 148 else:
138 title=status.name 149 title=status.name
139 150
140 return title, (status.state, status.errors) 151 return title, (status.state, status.errors), this_need_reconstruct
141 152
142 class BorgendTray(rumps.App): 153 class BorgendTray(rumps.App):
143 def __init__(self, backups): 154 def __init__(self, backups):
144 self.lock=Lock() 155 self.lock=Lock()
145 self.backups=backups 156 self.backups=backups
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'

mercurial