| 1 # |
1 # |
| 2 # MacOS UI |
2 # MacOS UI |
| 3 # |
3 # |
| 4 |
4 |
| 5 import rumps |
5 import rumps |
| |
6 import time |
| |
7 import datetime |
| |
8 import logging |
| |
9 from threading import Lock |
| |
10 |
| |
11 def make_title(status): |
| |
12 if status['type']=='scheduled': |
| |
13 # Operation scheduled |
| |
14 when=status['when'] |
| |
15 now=time.time() |
| |
16 if when<now: |
| |
17 whenstr='overdue' |
| |
18 else: |
| |
19 diff=datetime.timedelta(seconds=when-now) |
| |
20 if diff.days>0: |
| |
21 whenday=datetime.date.fromtimestamp(when) |
| |
22 whenstr='on %s' % whenday.isoformat() |
| |
23 else: |
| |
24 twhen=time.localtime(when) |
| |
25 if twhen.tm_sec>30: |
| |
26 # Round up minute display to avoid user confusion |
| |
27 twhen=time.localtime(when+30) |
| |
28 whenstr='at %02d:%02d' % (twhen.tm_hour, twhen.tm_min) |
| |
29 detail='' |
| |
30 if 'detail' in status and status['detail']: |
| |
31 detail=status['detail']+' ' |
| |
32 return "%s (%s%s %s)" % (status['name'], detail, status['operation'], whenstr) |
| |
33 elif status['type']=='current': |
| |
34 # Operation running |
| |
35 return "%s (running: %s)" % (status['name'], status['operation']) |
| |
36 else: # status['type']=='nothing': |
| |
37 # Should be unscheduled, nothing running |
| |
38 return status['name'] |
| |
39 |
| 6 |
40 |
| 7 class BorgendTray(rumps.App): |
41 class BorgendTray(rumps.App): |
| 8 def __init__(self, name, backups): |
42 def __init__(self, name, backups): |
| 9 menu=[rumps.MenuItem(b.name, callback=self.silly) for b in backups] |
43 self.lock=Lock() |
| 10 menu = menu + [rumps.MenuItem("Quit...", callback=self.my_quit_button)] |
44 self.backups=backups |
| 11 super().__init__(name, menu=menu, quit_button=None) |
45 self.statuses=[None]*len(backups) |
| 12 |
46 |
| 13 def my_quit_button(self, _): |
47 with self.lock: |
| |
48 # Initialise reporting callbacks and local status copy |
| |
49 # (since rumps doesn't seem to be able to update menu items |
| |
50 # without rebuilding the whole menu, and we don't want to lock |
| |
51 # when calling Backup.status(), especially as we will be called |
| |
52 # from Backup reporting its status, we keep a copy to allow |
| |
53 # rebuilding the entire menu |
| |
54 for index in range(len(backups)): |
| |
55 b=backups[index] |
| |
56 # Python closures suck dog's balls; hence the _index=index hack |
| |
57 # See also http://math.andrej.com/2009/04/09/pythons-lambda-is-broken/ |
| |
58 cb=(lambda obj, status, _index=index: |
| |
59 self.__status_callback(obj, _index, status)) |
| |
60 b.set_status_update_callback(cb) |
| |
61 self.statuses[index]=b.status() |
| |
62 |
| |
63 menu=self.__rebuild_menu() |
| |
64 |
| |
65 super().__init__(name, menu=menu, quit_button=None) |
| |
66 |
| |
67 def __rebuild_menu(self): |
| |
68 menu=[] |
| |
69 for index in range(len(self.backups)): |
| |
70 b=self.backups[index] |
| |
71 title=make_title(self.statuses[index]) |
| |
72 logging.info('TITLE: %s' % title) |
| |
73 # Python closures suck dog's balls... |
| |
74 # first and the last program I write in Python until somebody |
| |
75 # fixes this brain damage |
| |
76 cbm=lambda sender, _b=b: self.__menu_select_backup(sender, _b) |
| |
77 item=rumps.MenuItem(title, callback=cbm) |
| |
78 menu.append(item) |
| |
79 |
| |
80 menu_quit=rumps.MenuItem("Quit...", callback=self.my_quit) |
| |
81 menu.append(menu_quit) |
| |
82 |
| |
83 return menu |
| |
84 |
| |
85 |
| |
86 def my_quit(self, _): |
| 14 rumps.quit_application() |
87 rumps.quit_application() |
| 15 |
88 |
| 16 def silly(self, sender): |
89 def __menu_select_backup(self, sender, b): |
| 17 sender.state=not sender.state |
90 #sender.state=not sender.state |
| |
91 logging.debug("Manually backup '%s'", b.name) |
| |
92 b.create(None) |
| 18 |
93 |
| |
94 def __status_callback(self, obj, index, status): |
| |
95 logging.debug('Status callbackup %s' % str(status)) |
| |
96 with self.lock: |
| |
97 self.statuses[index]=status |
| |
98 logging.debug('Rebuilding menu') |
| |
99 self.menu.clear() |
| |
100 self.menu.update(self.__rebuild_menu()) |
| |
101 |
| |
102 |
| |
103 |