Turned Status and Operation into classes instead of dictionaries

Wed, 24 Jan 2018 22:04:16 +0000

author
Tuomo Valkonen <tuomov@iki.fi>
date
Wed, 24 Jan 2018 22:04:16 +0000
changeset 62
b7d13b2ad67e
parent 61
bc6c3d74e6ea
child 63
1fd6814a29fc

Turned Status and Operation into classes instead of dictionaries

backup.py file | annotate | diff | comparison | revisions
ui.py file | annotate | diff | comparison | revisions
--- 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()
--- 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 when<now:
             whenstr='overdue'
-            detail=''
+            info=''
         else:
             diff=datetime.timedelta(seconds=when-now)
             if diff.days>0:
@@ -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):

mercurial