| 6 import time |
6 import time |
| 7 import datetime |
7 import datetime |
| 8 import logging |
8 import logging |
| 9 from threading import Lock |
9 from threading import Lock |
| 10 |
10 |
| 11 traynames={False: 'B.', True: 'B!'} |
11 INACTIVE=0 |
| |
12 ACTIVE=1 |
| |
13 ERROR=2 |
| |
14 |
| |
15 traynames={INACTIVE: 'B.', ACTIVE: 'B!', ERROR: 'B?'} |
| |
16 |
| |
17 def combine_state(state1, state2): |
| |
18 if state1==ERROR or state2==ERROR: |
| |
19 return ERROR |
| |
20 elif state1==ACTIVE or state2==ACTIVE: |
| |
21 return ACTIVE |
| |
22 else: |
| |
23 return INACTIVE |
| 12 |
24 |
| 13 def make_title(status): |
25 def make_title(status): |
| 14 active=False |
26 state=INACTIVE |
| 15 if status['type']=='scheduled': |
27 if status['type']=='scheduled': |
| 16 # Operation scheduled |
28 # Operation scheduled |
| 17 when=status['when'] |
29 when=status['when'] |
| 18 now=time.time() |
30 now=time.time() |
| 19 if when<now: |
31 if when<now: |
| 29 # Round up minute display to avoid user confusion |
41 # Round up minute display to avoid user confusion |
| 30 twhen=time.localtime(when+30) |
42 twhen=time.localtime(when+30) |
| 31 whenstr='at %02d:%02d' % (twhen.tm_hour, twhen.tm_min) |
43 whenstr='at %02d:%02d' % (twhen.tm_hour, twhen.tm_min) |
| 32 detail='' |
44 detail='' |
| 33 if 'detail' in status and status['detail']: |
45 if 'detail' in status and status['detail']: |
| |
46 if status['detail']=='retry': |
| |
47 state=ERROR |
| 34 detail=status['detail']+' ' |
48 detail=status['detail']+' ' |
| 35 title="%s (%s%s %s)" % (status['name'], detail, status['operation'], whenstr) |
49 title="%s (%s%s %s)" % (status['name'], detail, status['operation'], whenstr) |
| 36 elif status['type']=='current': |
50 elif status['type']=='current': |
| 37 # Operation running |
51 # Operation running |
| 38 title="%s (running: %s)" % (status['name'], status['operation']) |
52 progress='' |
| 39 active=True |
53 if 'progress_current' in status and 'progress_total' in status: |
| |
54 progress='%d%%' % (status.progress_current/status.progress_total) |
| |
55 title="%s (running: %s%s)" % (status['name'], status['operation'], progress) |
| |
56 active=ACTIVE |
| 40 else: # status['type']=='nothing': |
57 else: # status['type']=='nothing': |
| 41 # Should be unscheduled, nothing running |
58 # Should be unscheduled, nothing running |
| 42 title=status['name'] |
59 title=status['name'] |
| 43 |
60 |
| 44 return title, active |
61 return title, state |
| 45 |
62 |
| 46 |
63 |
| 47 class BorgendTray(rumps.App): |
64 class BorgendTray(rumps.App): |
| 48 def __init__(self, name, backups): |
65 def __init__(self, name, backups): |
| 49 self.lock=Lock() |
66 self.lock=Lock() |
| 64 cb=(lambda obj, status, _index=index: |
81 cb=(lambda obj, status, _index=index: |
| 65 self.__status_callback(obj, _index, status)) |
82 self.__status_callback(obj, _index, status)) |
| 66 b.set_status_update_callback(cb) |
83 b.set_status_update_callback(cb) |
| 67 self.statuses[index]=b.status() |
84 self.statuses[index]=b.status() |
| 68 |
85 |
| 69 menu, active=self.__rebuild_menu() |
86 menu, state=self.__rebuild_menu() |
| 70 |
87 |
| 71 super().__init__(traynames[active], menu=menu, quit_button=None) |
88 super().__init__(traynames[state], menu=menu, quit_button=None) |
| 72 |
89 |
| 73 def __rebuild_menu(self): |
90 def __rebuild_menu(self): |
| 74 menu=[] |
91 menu=[] |
| 75 active=False |
92 state=INACTIVE |
| 76 for index in range(len(self.backups)): |
93 for index in range(len(self.backups)): |
| 77 b=self.backups[index] |
94 b=self.backups[index] |
| 78 title, this_active=make_title(self.statuses[index]) |
95 title, this_state=make_title(self.statuses[index]) |
| 79 logging.info('TITLE: %s' % title) |
96 logging.info('TITLE: %s' % title) |
| 80 # Python closures suck dog's balls... |
97 # Python closures suck dog's balls... |
| 81 # first and the last program I write in Python until somebody |
98 # first and the last program I write in Python until somebody |
| 82 # fixes this brain damage |
99 # fixes this brain damage |
| 83 cbm=lambda sender, _b=b: self.__menu_select_backup(sender, _b) |
100 cbm=lambda sender, _b=b: self.__menu_select_backup(sender, _b) |
| 84 item=rumps.MenuItem(title, callback=cbm) |
101 item=rumps.MenuItem(title, callback=cbm) |
| 85 menu.append(item) |
102 menu.append(item) |
| 86 active=active or this_active |
103 state=combine_state(state, this_state) |
| 87 |
104 |
| 88 menu_quit=rumps.MenuItem("Quit...", callback=self.my_quit) |
105 menu_quit=rumps.MenuItem("Quit...", callback=self.my_quit) |
| 89 menu.append(menu_quit) |
106 menu.append(menu_quit) |
| 90 |
107 |
| 91 return menu, active |
108 return menu, state |
| 92 |
109 |
| 93 |
110 |
| 94 def my_quit(self, _): |
111 def my_quit(self, _): |
| 95 rumps.quit_application() |
112 rumps.quit_application() |
| 96 |
113 |