| 6 import time |
6 import time |
| 7 import datetime |
7 import datetime |
| 8 import logging |
8 import logging |
| 9 import borgend |
9 import borgend |
| 10 import utils |
10 import utils |
| |
11 import backup |
| 11 from threading import Lock, Timer |
12 from threading import Lock, Timer |
| 12 from config import settings |
13 from config import settings |
| 13 import objc |
14 import objc |
| 14 |
15 |
| 15 logger=borgend.logger.getChild(__name__) |
16 logger=borgend.logger.getChild(__name__) |
| 16 |
17 |
| 17 INACTIVE=0 |
|
| 18 SCHEDULED_OK=1 |
|
| 19 ACTIVE=2 |
|
| 20 OFFLINE=3 |
|
| 21 ERRORS=4 |
|
| 22 |
|
| 23 traynames={ |
18 traynames={ |
| 24 INACTIVE: 'B.', |
19 backup.INACTIVE: 'B.', |
| 25 SCHEDULED_OK: 'B.', |
20 backup.SCHEDULED: 'B.', |
| 26 ACTIVE: 'B!', |
21 backup.ACTIVE: 'B!', |
| 27 OFFLINE: 'B⦙', |
22 backup.BUSY: 'B⦙', |
| 28 ERRORS: 'B?' |
23 backup.OFFLINE: 'B⦙', |
| |
24 backup.ERRORS: 'B?' |
| |
25 } |
| |
26 |
| |
27 statestring={ |
| |
28 backup.INACTIVE: 'inactive', |
| |
29 backup.SCHEDULED: 'scheduled', |
| |
30 backup.ACTIVE: 'active', |
| |
31 backup.BUSY: 'busy', |
| |
32 backup.OFFLINE: 'offline', |
| |
33 backup.ERRORS: 'errors' |
| 29 } |
34 } |
| 30 |
35 |
| 31 # Refresh the menu at most once a second to reduce flicker |
36 # Refresh the menu at most once a second to reduce flicker |
| 32 refresh_interval=1.0 |
37 refresh_interval=1.0 |
| 33 |
|
| 34 def combine_state(state1, state2): |
|
| 35 return max(state1, state2) |
|
| 36 |
38 |
| 37 # Workaround to rumps brokenness; |
39 # Workaround to rumps brokenness; |
| 38 # see https://github.com/jaredks/rumps/issues/59 |
40 # see https://github.com/jaredks/rumps/issues/59 |
| 39 def notification_workaround(title, subtitle, message): |
41 def notification_workaround(title, subtitle, message): |
| 40 try: |
42 try: |
| 86 if twhen.tm_sec>30: |
87 if twhen.tm_sec>30: |
| 87 # Round up minute display to avoid user confusion |
88 # Round up minute display to avoid user confusion |
| 88 twhen=time.localtime(when+30) |
89 twhen=time.localtime(when+30) |
| 89 whenstr='at %02d:%02d' % (twhen.tm_hour, twhen.tm_min) |
90 whenstr='at %02d:%02d' % (twhen.tm_hour, twhen.tm_min) |
| 90 |
91 |
| 91 if status['detail']=='normal': |
92 detail='' |
| 92 state=SCHEDULED_OK |
93 if state>=backup.BUSY and state in statestring: |
| 93 detail='' |
94 detail=statestring[state] + '; ' |
| 94 else: |
95 if status['detail']!='normal': |
| 95 if status['detail']=='retry': |
96 detail=detail+status['detail']+' ' |
| 96 state=ERRORS |
|
| 97 detail=status['detail']+' ' |
|
| 98 title="%s (%s%s %s)" % (status['name'], detail, status['operation'], whenstr) |
97 title="%s (%s%s %s)" % (status['name'], detail, status['operation'], whenstr) |
| 99 elif status['type']=='current': |
98 elif status['type']=='current': |
| 100 # Operation running |
99 # Operation running |
| 101 progress='' |
100 progress='' |
| 102 if 'progress_current' in status and 'progress_total' in status: |
101 if 'progress_current' in status and 'progress_total' in status: |
| 103 progress=' %d%%' % (status['progress_current']/status['progress_total']) |
102 progress=' %d%%' % (status['progress_current']/status['progress_total']) |
| 104 elif 'original_size' in status and 'deduplicated_size' in status: |
103 elif 'original_size' in status and 'deduplicated_size' in status: |
| 105 progress=' %s→%s' % (humanbytes(status['original_size']), |
104 progress=' %s→%s' % (humanbytes(status['original_size']), |
| 106 humanbytes(status['deduplicated_size'])) |
105 humanbytes(status['deduplicated_size'])) |
| 107 title="%s (running: %s%s)" % (status['name'], status['operation'], progress) |
106 title="%s (running: %s%s)" % (status['name'], status['operation'], progress) |
| 108 state=ACTIVE |
|
| 109 else: # status['type']=='nothing': |
107 else: # status['type']=='nothing': |
| 110 # Should be unscheduled, nothing running |
108 # Should be unscheduled, nothing running |
| 111 title=status['name'] |
109 detail='' |
| 112 if status['errors']: |
110 if state>=backup.BUSY and state in statestring: |
| 113 title=title + " (errors)" |
111 detail=' (' + statestring[state] + ')' |
| 114 |
112 title=status['name'] + detail |
| 115 if status['errors']: |
|
| 116 state=ERRORS |
|
| 117 |
113 |
| 118 return title, state |
114 return title, state |
| 119 |
115 |
| 120 class BorgendTray(rumps.App): |
116 class BorgendTray(rumps.App): |
| 121 def __init__(self, backups): |
117 def __init__(self, backups): |
| 145 |
141 |
| 146 super().__init__(traynames[state], menu=menu, quit_button=None) |
142 super().__init__(traynames[state], menu=menu, quit_button=None) |
| 147 |
143 |
| 148 def __rebuild_menu(self): |
144 def __rebuild_menu(self): |
| 149 menu=[] |
145 menu=[] |
| 150 state=INACTIVE |
146 state=backup.INACTIVE |
| 151 for index in range(len(self.backups)): |
147 for index in range(len(self.backups)): |
| 152 b=self.backups[index] |
148 b=self.backups[index] |
| 153 title, this_state=make_title(self.statuses[index]) |
149 title, this_state=make_title(self.statuses[index]) |
| 154 # Python closures suck dog's balls... |
150 # Python closures suck dog's balls... |
| 155 # first and the last program I write in Python until somebody |
151 # first and the last program I write in Python until somebody |
| 156 # fixes this brain damage |
152 # fixes this brain damage |
| 157 cbm=lambda sender, _b=b: self.__menu_select_backup(sender, _b) |
153 cbm=lambda sender, _b=b: self.__menu_select_backup(sender, _b) |
| 158 item=rumps.MenuItem(title, callback=cbm) |
154 item=rumps.MenuItem(title, callback=cbm) |
| 159 if this_state==SCHEDULED_OK: |
155 if this_state==backup.SCHEDULED: |
| 160 item.state=1 |
156 item.state=1 |
| 161 elif this_state>=OFFLINE: |
157 elif this_state>=backup.BUSY: |
| 162 item.state=-1 |
158 item.state=-1 |
| 163 menu.append(item) |
159 menu.append(item) |
| 164 state=combine_state(state, this_state) |
160 state=backup.combine_state(state, this_state) |
| 165 |
161 |
| 166 menu_log=rumps.MenuItem("Show log", callback=lambda _: showlog()) |
162 menu_log=rumps.MenuItem("Show log", callback=lambda _: showlog()) |
| 167 menu.append(menu_log) |
163 menu.append(menu_log) |
| 168 |
164 |
| 169 if not settings['no_quit_menu_entry']: |
165 if not settings['no_quit_menu_entry']: |