--- a/ui.py Sat Jan 20 15:55:09 2018 +0000 +++ b/ui.py Sat Jan 20 19:57:05 2018 +0000 @@ -3,16 +3,101 @@ # import rumps +import time +import datetime +import logging +from threading import Lock + +def make_title(status): + if status['type']=='scheduled': + # Operation scheduled + when=status['when'] + now=time.time() + if when<now: + whenstr='overdue' + else: + diff=datetime.timedelta(seconds=when-now) + if diff.days>0: + whenday=datetime.date.fromtimestamp(when) + whenstr='on %s' % whenday.isoformat() + else: + twhen=time.localtime(when) + if twhen.tm_sec>30: + # Round up minute display to avoid user confusion + twhen=time.localtime(when+30) + whenstr='at %02d:%02d' % (twhen.tm_hour, twhen.tm_min) + detail='' + if 'detail' in status and status['detail']: + detail=status['detail']+' ' + return "%s (%s%s %s)" % (status['name'], detail, status['operation'], whenstr) + elif status['type']=='current': + # Operation running + return "%s (running: %s)" % (status['name'], status['operation']) + else: # status['type']=='nothing': + # Should be unscheduled, nothing running + return status['name'] + class BorgendTray(rumps.App): def __init__(self, name, backups): - menu=[rumps.MenuItem(b.name, callback=self.silly) for b in backups] - menu = menu + [rumps.MenuItem("Quit...", callback=self.my_quit_button)] - super().__init__(name, menu=menu, quit_button=None) + self.lock=Lock() + self.backups=backups + self.statuses=[None]*len(backups) + + with self.lock: + # Initialise reporting callbacks and local status copy + # (since rumps doesn't seem to be able to update menu items + # without rebuilding the whole menu, and we don't want to lock + # when calling Backup.status(), especially as we will be called + # from Backup reporting its status, we keep a copy to allow + # rebuilding the entire menu + for index in range(len(backups)): + b=backups[index] + # Python closures suck dog's balls; hence the _index=index hack + # See also http://math.andrej.com/2009/04/09/pythons-lambda-is-broken/ + cb=(lambda obj, status, _index=index: + self.__status_callback(obj, _index, status)) + b.set_status_update_callback(cb) + self.statuses[index]=b.status() + + menu=self.__rebuild_menu() - def my_quit_button(self, _): + super().__init__(name, menu=menu, quit_button=None) + + def __rebuild_menu(self): + menu=[] + for index in range(len(self.backups)): + b=self.backups[index] + title=make_title(self.statuses[index]) + logging.info('TITLE: %s' % title) + # Python closures suck dog's balls... + # first and the last program I write in Python until somebody + # fixes this brain damage + cbm=lambda sender, _b=b: self.__menu_select_backup(sender, _b) + item=rumps.MenuItem(title, callback=cbm) + menu.append(item) + + menu_quit=rumps.MenuItem("Quit...", callback=self.my_quit) + menu.append(menu_quit) + + return menu + + + def my_quit(self, _): rumps.quit_application() - def silly(self, sender): - sender.state=not sender.state + def __menu_select_backup(self, sender, b): + #sender.state=not sender.state + logging.debug("Manually backup '%s'", b.name) + b.create(None) + def __status_callback(self, obj, index, status): + logging.debug('Status callbackup %s' % str(status)) + with self.lock: + self.statuses[index]=status + logging.debug('Rebuilding menu') + self.menu.clear() + self.menu.update(self.__rebuild_menu()) + + +