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 |