# HG changeset patch # User Tuomo Valkonen # Date 1516831456 0 # Node ID b7d13b2ad67ea37838a3507e621a8a7bed10b9a0 # Parent bc6c3d74e6eaaa2a2087edc353fb420d4c2f5b26 Turned Status and Operation into classes instead of dictionaries diff -r bc6c3d74e6ea -r b7d13b2ad67e backup.py --- a/backup.py Wed Jan 24 20:18:45 2018 +0000 +++ b/backup.py Wed Jan 24 22:04:16 2018 +0000 @@ -15,6 +15,10 @@ logger=borgend.logger.getChild(__name__) +# +# State and operation related helper classes +# + class State(IntEnum): # State INACTIVE=0 @@ -45,6 +49,42 @@ Errors.ERRORS: 'errors' } +class Operation: + CREATE='create' + PRUNE='prune' + def __init__(self, operation, when_monotonic, **kwargs): + self.operation=operation + self.when_monotonic=when_monotonic + self.detail=kwargs + + def when(self): + return self.when_monotonic-time.monotonic()+time.time() + + +class Status(Operation): + def __init__(self, backup, op=None): + if op: + super().__init__(op.operation, op.when_monotonic, + detail=op.detail) + else: + super().__init__(None, None) + + self.name=backup.name + self.state=backup.state + self.errors=backup.errors + +# +# Miscellaneous helper routines +# + +loglevel_translation={ + 'CRITICAL': logging.CRITICAL, + 'ERROR': logging.ERROR, + 'WARNING': logging.WARNING, + 'DEBUG': logging.DEBUG, + 'INFO': logging.INFO +} + def translate_loglevel(x): if x in loglevel_translation: return loglevel_translation[x] @@ -58,13 +98,9 @@ return tmp return None -loglevel_translation={ - 'CRITICAL': logging.CRITICAL, - 'ERROR': logging.ERROR, - 'WARNING': logging.WARNING, - 'DEBUG': logging.DEBUG, - 'INFO': logging.INFO -} +# +# The Backup class +# class Backup(TerminableThread): @@ -193,8 +229,8 @@ total=safe_get_int(msg, 'total') if current is not None and total is not None: with self._cond: - self.current_operation['progress_current']=current - self.current_operation['progress_total']=total + self.current_operation.detail['progress_current']=current + self.current_operation.detail['progress_total']=total status, callback=self.__status_unlocked() elif t=='archive_progress': @@ -203,9 +239,9 @@ deduplicated_size=safe_get_int(msg, 'deduplicated_size') if original_size is not None and original_size is not None and deduplicated_size is not None: with self._cond: - self.current_operation['original_size']=original_size - self.current_operation['compressed_size']=compressed_size - self.current_operation['deduplicated_size']=deduplicated_size + self.current_operation.detail['original_size']=original_size + self.current_operation.detail['compressed_size']=compressed_size + self.current_operation.detail['deduplicated_size']=deduplicated_size status, callback=self.__status_unlocked() elif t=='progress_message': @@ -274,8 +310,8 @@ self.logger.debug('Borg subprocess terminated (errors state: %s); terminating result listener thread' % str(errors)) with self._cond: - if self.current_operation['operation']=='create': - self.lastrun_when=self.current_operation['when_monotonic'] + if self.current_operation.operation=='create': + self.lastrun_when=self.current_operation.when_monotonic self.thread_res=None self.thread_log=None self.borg_instance=None @@ -288,7 +324,7 @@ def __do_launch(self, op, archive_or_repository, *args): passphrase=self.extract_passphrase() - inst=BorgInstance(op['operation'], archive_or_repository, *args) + inst=BorgInstance(op.operation, archive_or_repository, *args) inst.launch(passphrase=passphrase) self.logger.debug('Creating listener threads') @@ -307,9 +343,9 @@ t_res.start() def __launch(self, op): - self.logger.debug("Launching '%s'" % op['operation']) + self.logger.debug("Launching '%s'" % str(op.operation)) - if op['operation']=='create': + if op.operation==Operation.CREATE: archive="%s::%s%s" % (self.repository.repository_name, self.archive_prefix, self.archive_template) @@ -317,13 +353,13 @@ self.__do_launch(op, archive, self.common_parameters+self.create_parameters, self.paths) - elif op['operation']=='prune': + elif op.operation==Operation.PRUNE: self.__do_launch(op, self.repository.repository_name, ([{'prefix': self.archive_prefix}] + self.common_parameters + self.prune_parameters)) else: - raise NotImplementedError("Invalid operation '%s'" % op['operation']) + raise NotImplementedError("Invalid operation '%s'" % str(op.operation)) # This must be called with self._cond held. def __launch_check(self): @@ -336,7 +372,9 @@ self.__launch(op) self.current_operation=op - self.current_operation['when_monotonic']=time.monotonic() + # Update scheduled time to real starting time to schedule + # next run relative to this + self.current_operation.when_monotonic=time.monotonic() self.state=State.ACTIVE # Reset error status when starting a new operation self.errors=Errors.OK @@ -393,9 +431,9 @@ op=self.__next_operation_unlocked() if op: now=time.monotonic() - delay=max(0, op['when_monotonic']-now) + delay=max(0, op.when_monotonic-now) self.logger.info("Scheduling '%s' (detail: %s) in %d seconds" % - (op['operation'], op['detail'], delay)) + (str(op.operation), op.detail or 'none', delay)) self.scheduled_operation=op self.state=State.SCHEDULED @@ -435,44 +473,31 @@ if initial_interval==0: return None else: - return {'operation': 'create', - 'detail': 'initial', - 'when_monotonic': now+initial_interval} + return Operation(Operation.CREATE, now+initial_interval, + reason='initial') elif not self.errors.ok(): if self.retry_interval==0: return None else: - return {'operation': 'create', - 'detail': 'retry', - 'when_monotonic': self.lastrun_when+self.retry_interval} + return Operation(Operation.CREATE, + self.lastrun_when+self.retry_interval, + reason='retry') else: if self.backup_interval==0: return None else: - return {'operation': 'create', - 'detail': 'normal', - 'when_monotonic': self.lastrun_when+self.backup_interval} + return Operation(Operation.CREATE, + self.lastrun_when+self.backup_interval) def __status_unlocked(self): callback=self.__status_update_callback if self.current_operation: - status=self.current_operation + status=Status(self, self.current_operation) elif self.scheduled_operation: - status=self.scheduled_operation + status=Status(self, self.scheduled_operation) else: - status={'type': 'nothing'} - - status['name']=self._name - status['state']=self.state - status['errors']=self.errors - - if 'detail' not in status: - status['detail']='NONE' - - if 'when_monotonic' in status: - status['when']=(status['when_monotonic'] - -time.monotonic()+time.time()) + status=Status(self) return status, callback @@ -501,13 +526,13 @@ return res[0] def create(self): - op={'operation': 'create', 'detail': 'manual'} + op=Operation(Operation.CREATE, time.monotonic(), reason='manual') with self._cond: self.scheduled_operation=op self._cond.notify() def prune(self): - op={'operation': 'prune', 'detail': 'manual'} + op=Operation(Operation.PRUNE, time.monotonic(), reason='manual') with self._cond: self.scheduled_operation=op self._cond.notify() diff -r bc6c3d74e6ea -r b7d13b2ad67e ui.py --- a/ui.py Wed Jan 24 20:18:45 2018 +0000 +++ b/ui.py Wed Jan 24 22:04:16 2018 +0000 @@ -76,25 +76,24 @@ return '{0:.2f}TB'.format(B/TB) def make_title(status): - def add_detail(detail, new): - if detail: - return "%s; %s" % (detail, new) + def add_info(info, new): + if info: + return "%s; %s" % (info, new) else: return new - errors=status['errors'] - detail=None + info=None - if not errors.ok(): - detail=add_detail(detail, str(errors)) + if not status.errors.ok(): + info=add_info(info, str(status.errors)) - if status['state']==backup.State.SCHEDULED: + if status.state==backup.State.SCHEDULED: # Operation scheduled - when=status['when'] + when=status.when() now=time.time() if when0: @@ -107,37 +106,38 @@ twhen=time.localtime(when+30) whenstr='at %02d:%02d' % (twhen.tm_hour, twhen.tm_min) - this_detail='' - if status['detail']!='normal': - this_detail=status['detail'] + ' ' + this_info='' + if 'reason' in status.detail: + this_info=status.detail['reason'] + ' ' - when_how_sched= "%s%s %s" % (this_detail, status['operation'], whenstr) + when_how_sched= "%s%s %s" % (this_info, status.operation, whenstr) - detail=add_detail(detail, when_how_sched) + info=add_info(info, when_how_sched) - elif status['state']==backup.State.QUEUED: - detail=add_detail(detail, "queued") - elif status['state']==backup.State.ACTIVE: + elif status.state==backup.State.QUEUED: + info=add_info(info, "queued") + elif status.state==backup.State.ACTIVE: # Operation running progress='' - if 'progress_current' in status and 'progress_total' in status: - progress=' %d%%' % (status['progress_current']/status['progress_total']) - elif 'original_size' in status and 'deduplicated_size' in status: - progress=' %s→%s' % (humanbytes(status['original_size']), - humanbytes(status['deduplicated_size'])) + d=status.detail + if 'progress_current' in d and 'progress_total' in d: + progress=' %d%%' % (d['progress_current']/d['progress_total']) + elif 'original_size' in d and 'deduplicated_size' in d: + progress=' %s→%s' % (humanbytes(d['original_size']), + humanbytes(d['deduplicated_size'])) - howrunning = "running %s%s" % (status['operation'], progress) + howrunning = "running %s%s" % (status.operation, progress) - detail=add_detail(detail, howrunning) + info=add_info(info, howrunning) else: pass - if detail: - title=status['name'] + ' (' + detail + ')' + if info: + title=status.name + ' (' + info + ')' else: - title=status['name'] + title=status.name - return title, (status['state'], status['errors']) + return title, (status.state, status.errors) class BorgendTray(rumps.App): def __init__(self, backups):