ui.py

changeset 61
bc6c3d74e6ea
parent 55
407af23d16bb
child 62
b7d13b2ad67e
equal deleted inserted replaced
60:10bd7e3906d9 61:bc6c3d74e6ea
12 from config import settings 12 from config import settings
13 import objc 13 import objc
14 14
15 logger=borgend.logger.getChild(__name__) 15 logger=borgend.logger.getChild(__name__)
16 16
17 traynames={ 17 traynames_ok={
18 backup.INACTIVE: 'B.', 18 backup.State.INACTIVE: 'B.',
19 backup.SCHEDULED: 'B.', 19 backup.State.SCHEDULED: 'B.',
20 backup.QUEUED: 'B:', 20 backup.State.QUEUED: 'B:',
21 backup.ACTIVE: 'B!', 21 backup.State.ACTIVE: 'B!',
22 backup.BUSY: 'B⦙',
23 backup.OFFLINE: 'B⦙',
24 backup.ERRORS: 'B?'
25 } 22 }
26 23
27 statestring={ 24 traynames_errors={
28 backup.INACTIVE: 'inactive', 25 # The first one should never be used
29 backup.SCHEDULED: 'scheduled', 26 backup.Errors.OK: traynames_ok[backup.State.INACTIVE],
30 backup.QUEUED: 'queued', 27 backup.Errors.BUSY: 'B⦙',
31 backup.ACTIVE: 'active', 28 backup.Errors.OFFLINE: 'B⦙',
32 backup.BUSY: 'busy', 29 backup.Errors.ERRORS: 'B?'
33 backup.OFFLINE: 'offline',
34 backup.ERRORS: 'errors'
35 } 30 }
31
32 def trayname(ste):
33 state=ste[0]
34 errors=ste[1]
35 if not errors.ok():
36 return traynames_errors[errors]
37 else:
38 return traynames_ok[state]
39
40 def combine_state(a, b):
41 return (max(a[0], b[0]), max(a[1], b[1]))
36 42
37 # Refresh the menu at most once a second to reduce flicker 43 # Refresh the menu at most once a second to reduce flicker
38 refresh_interval=1.0 44 refresh_interval=1.0
39 45
40 # Workaround to rumps brokenness; 46 # Workaround to rumps brokenness;
68 return '{0:.2f}GB'.format(B/GB) 74 return '{0:.2f}GB'.format(B/GB)
69 elif TB <= B: 75 elif TB <= B:
70 return '{0:.2f}TB'.format(B/TB) 76 return '{0:.2f}TB'.format(B/TB)
71 77
72 def make_title(status): 78 def make_title(status):
73 state=status['state'] 79 def add_detail(detail, new):
74 detail='' 80 if detail:
75 if status['type']=='scheduled': 81 return "%s; %s" % (detail, new)
82 else:
83 return new
84
85 errors=status['errors']
86 detail=None
87
88 if not errors.ok():
89 detail=add_detail(detail, str(errors))
90
91 if status['state']==backup.State.SCHEDULED:
76 # Operation scheduled 92 # Operation scheduled
77 when=status['when'] 93 when=status['when']
78 now=time.time() 94 now=time.time()
79 if when<now: 95 if when<now:
80 whenstr='overdue' 96 whenstr='overdue'
89 if twhen.tm_sec>30: 105 if twhen.tm_sec>30:
90 # Round up minute display to avoid user confusion 106 # Round up minute display to avoid user confusion
91 twhen=time.localtime(when+30) 107 twhen=time.localtime(when+30)
92 whenstr='at %02d:%02d' % (twhen.tm_hour, twhen.tm_min) 108 whenstr='at %02d:%02d' % (twhen.tm_hour, twhen.tm_min)
93 109
94 detail='' 110 this_detail=''
95 if state>=backup.BUSY and state in statestring:
96 detail=statestring[state] + '; '
97 if status['detail']!='normal': 111 if status['detail']!='normal':
98 detail=detail+status['detail']+' ' 112 this_detail=status['detail'] + ' '
99 title="%s (%s%s %s)" % (status['name'], detail, status['operation'], whenstr) 113
100 elif status['type']=='queued': 114 when_how_sched= "%s%s %s" % (this_detail, status['operation'], whenstr)
101 title="%s (queued)" % status['name'] 115
102 elif status['type']=='current': 116 detail=add_detail(detail, when_how_sched)
117
118 elif status['state']==backup.State.QUEUED:
119 detail=add_detail(detail, "queued")
120 elif status['state']==backup.State.ACTIVE:
103 # Operation running 121 # Operation running
104 progress='' 122 progress=''
105 if 'progress_current' in status and 'progress_total' in status: 123 if 'progress_current' in status and 'progress_total' in status:
106 progress=' %d%%' % (status['progress_current']/status['progress_total']) 124 progress=' %d%%' % (status['progress_current']/status['progress_total'])
107 elif 'original_size' in status and 'deduplicated_size' in status: 125 elif 'original_size' in status and 'deduplicated_size' in status:
108 progress=' %s→%s' % (humanbytes(status['original_size']), 126 progress=' %s→%s' % (humanbytes(status['original_size']),
109 humanbytes(status['deduplicated_size'])) 127 humanbytes(status['deduplicated_size']))
110 title="%s (running: %s%s)" % (status['name'], status['operation'], progress) 128
111 else: # status['type']=='nothing': 129 howrunning = "running %s%s" % (status['operation'], progress)
112 # Should be unscheduled, nothing running 130
113 detail='' 131 detail=add_detail(detail, howrunning)
114 if state>=backup.BUSY and state in statestring: 132 else:
115 detail=' (' + statestring[state] + ')' 133 pass
116 title=status['name'] + detail 134
117 135 if detail:
118 return title, state 136 title=status['name'] + ' (' + detail + ')'
137 else:
138 title=status['name']
139
140 return title, (status['state'], status['errors'])
119 141
120 class BorgendTray(rumps.App): 142 class BorgendTray(rumps.App):
121 def __init__(self, backups): 143 def __init__(self, backups):
122 self.lock=Lock() 144 self.lock=Lock()
123 self.backups=backups 145 self.backups=backups
141 163
142 menu, state=self.__rebuild_menu() 164 menu, state=self.__rebuild_menu()
143 165
144 self.refresh_timer=None 166 self.refresh_timer=None
145 167
146 super().__init__(traynames[state], menu=menu, quit_button=None) 168 super().__init__(trayname(state), menu=menu, quit_button=None)
147 169
148 def __rebuild_menu(self): 170 def __rebuild_menu(self):
149 menu=[] 171 menu=[]
150 state=backup.INACTIVE 172 state=(backup.State.INACTIVE, backup.Errors.OK)
151 for index in range(len(self.backups)): 173 for index in range(len(self.backups)):
152 b=self.backups[index] 174 b=self.backups[index]
153 title, this_state=make_title(self.statuses[index]) 175 title, this_state=make_title(self.statuses[index])
154 # Python closures suck dog's balls... 176 # Python closures suck dog's balls...
155 # first and the last program I write in Python until somebody 177 # first and the last program I write in Python until somebody
156 # fixes this brain damage 178 # fixes this brain damage
157 cbm=lambda sender, _b=b: self.__menu_select_backup(sender, _b) 179 cbm=lambda sender, _b=b: self.__menu_select_backup(sender, _b)
158 item=rumps.MenuItem(title, callback=cbm) 180 item=rumps.MenuItem(title, callback=cbm)
159 if this_state==backup.SCHEDULED: 181 if not this_state[1].ok():
182 item.state=-1
183 elif this_state[0]==backup.State.SCHEDULED or this_state[0]==backup.State.QUEUED:
160 item.state=1 184 item.state=1
161 elif this_state>=backup.BUSY:
162 item.state=-1
163 menu.append(item) 185 menu.append(item)
164 state=backup.combine_state(state, this_state) 186 state=combine_state(state, this_state)
165 187
166 menu_log=rumps.MenuItem("Show log", callback=lambda _: showlog()) 188 menu_log=rumps.MenuItem("Show log", callback=lambda _: showlog())
167 menu.append(menu_log) 189 menu.append(menu_log)
168 190
169 if not settings['no_quit_menu_entry']: 191 if not settings['no_quit_menu_entry']:
174 196
175 def refresh_ui(self): 197 def refresh_ui(self):
176 with self.lock: 198 with self.lock:
177 self.refresh_timer=None 199 self.refresh_timer=None
178 logger.debug('Rebuilding menu') 200 logger.debug('Rebuilding menu')
179 menu, active=self.__rebuild_menu() 201 menu, state=self.__rebuild_menu()
180 self.menu.clear() 202 self.menu.clear()
181 self.menu.update(menu) 203 self.menu.update(menu)
182 self.title=traynames[active] 204 self.title=trayname(state)
183 205
184 def __status_callback(self, index, status, errorlog=None): 206 def __status_callback(self, index, status, errorlog=None):
185 logger.debug('Status callback: %s' % str(status)) 207 logger.debug('Status callback: %s' % str(status))
186 208
187 with self.lock: 209 with self.lock:

mercurial