Error handling improvements

Sun, 21 Jan 2018 14:34:35 +0000

author
Tuomo Valkonen <tuomov@iki.fi>
date
Sun, 21 Jan 2018 14:34:35 +0000
changeset 32
06fc14211ba9
parent 31
b4b4bb7a2ec5
child 33
91421eeb4426

Error handling improvements

backup.py file | annotate | diff | comparison | revisions
instance.py file | annotate | diff | comparison | revisions
ui.py file | annotate | diff | comparison | revisions
--- a/backup.py	Sun Jan 21 13:34:12 2018 +0000
+++ b/backup.py	Sun Jan 21 14:34:35 2018 +0000
@@ -78,16 +78,30 @@
                                                     'Keychain account', self.loc,
                                                     default='')
 
+        self.__passphrase=None
+
         if config.settings['extract_passphrases_at_startup']:
-            self.extract_passphrase()
+            try:
+                self.extract_passphrase()
+            except Exception:
+                pass
 
     def extract_passphrase(self):
         acc=self.__keychain_account
-        if acc and acc!='':
-            pw=keyring.get_password("borg-backup", acc)
-            self.__passphrase=pw
-        else:
-            self.__passphrase=None
+        if not self.__passphrase:
+            if acc and acc!='':
+                logger.debug('Requesting passphrase')
+                try:
+                    pw=keyring.get_password("borg-backup", acc)
+                except Exception as err:
+                    logger.error('Failed to retrieve password: %s' % str(err))
+                    raise err
+                else:
+                    logger.debug('Received passphrase')
+                self.__passphrase=pw
+            else:
+                self.__passphrase=None
+        return self.__passphrase
 
     def __init__(self, identifier, cfg):
         self.identifier=identifier
@@ -95,7 +109,7 @@
         self.__decode_config(cfg)
 
         self.config=config
-        self.lastrun=None
+        self.lastrun_when=None
         self.lastrun_success=None
         self.borg_instance=None
         self.current_operation=None
@@ -212,7 +226,7 @@
 
         with self.lock:
             if self.current_operation['operation']=='create':
-                self.lastrun=self.current_operation['when_monotonic']
+                self.lastrun_when=self.current_operation['when_monotonic']
                 self.lastrun_success=success
             self.thread_res=None
             self.thread_log=None
@@ -224,10 +238,10 @@
             callback(self, status)
 
     def __do_launch(self, queue, op, archive_or_repository, *args):
-        self.extract_passphrase()
+        passphrase=self.extract_passphrase()
 
         inst=BorgInstance(op['operation'], archive_or_repository, *args)
-        inst.launch(passphrase=self.__passphrase)
+        inst.launch(passphrase=passphrase)
 
         logger.debug('Creating listener threads')
 
@@ -258,24 +272,30 @@
                 self.timer=None
                 self.scheduled_operation=None
 
-            logger.debug("Launching '%s' on '%s'" % (op['operation'], self.name))
+            try:
+                logger.debug("Launching '%s' on '%s'" % (op['operation'], self.name))
 
-            if op['operation']=='create':
-                archive="%s::%s%s" % (self.repository,
-                                      self.archive_prefix,
-                                      self.archive_template)
+                if op['operation']=='create':
+                    archive="%s::%s%s" % (self.repository,
+                                          self.archive_prefix,
+                                          self.archive_template)
 
-                self.__do_launch(queue, op, archive,
-                                 self.common_parameters+self.create_parameters,
-                                 self.paths)
-            elif op['operation']=='prune':
-                self.__do_launch(queue, op, self.repository,
-                                 ([{'prefix': self.archive_prefix}] + 
-                                  self.common_parameters +
-                                  self.prune_parameters))
-            else:
-                logger.error("Invalid operaton '%s'" % op['operation'])
+                    self.__do_launch(queue, op, archive,
+                                     self.common_parameters+self.create_parameters,
+                                     self.paths)
+                elif op['operation']=='prune':
+                    self.__do_launch(queue, op, self.repository,
+                                     ([{'prefix': self.archive_prefix}] + 
+                                      self.common_parameters +
+                                      self.prune_parameters))
+                else:
+                    raise NotImplementedError("Invalid operation '%s'" % op['operation'])
+            except Exception as err:
+                logging.debug('Rescheduling after failure')
+                self.lastrun_when=time.monotonic()
+                self.lastrun_success=False
                 self.__schedule_unlocked()
+                raise err
 
             return True
 
@@ -338,7 +358,7 @@
     def __next_operation_unlocked(self):
         # TODO: pruning as well
         now=time.monotonic()
-        if not self.lastrun:
+        if not self.lastrun_when:
             initial_interval=self.retry_interval
             if initial_interval==0:
                 initial_interval=self.backup_interval
@@ -354,14 +374,14 @@
             else:
                 return {'operation': 'create',
                         'detail': 'retry',
-                        'when_monotonic': self.lastrun+self.retry_interval}
+                        'when_monotonic': self.lastrun_when+self.retry_interval}
         else:
             if self.backup_interval==0:
                 return None
             else:
                 return {'operation': 'create',
                         'detail': None,
-                        'when_monotonic': self.lastrun+self.backup_interval}
+                        'when_monotonic': self.lastrun_when+self.backup_interval}
 
     def __schedule_unlocked(self):
         if self.current_operation:
--- a/instance.py	Sun Jan 21 13:34:12 2018 +0000
+++ b/instance.py	Sun Jan 21 14:34:35 2018 +0000
@@ -43,9 +43,11 @@
 
         logger.info('Launching ' + str(cmd))
 
+        # Set passphrase if not, or set to empty if not known, so borg
+        # won't hang waiting for it, which seems to happen even if we
+        # close stdin.
         env=os.environ.copy()
-        if passphrase:
-            env['BORG_PASSPHRASE']=passphrase
+        env['BORG_PASSPHRASE']=passphrase or ''
 
         # Workaround: if launched is a standalone app created with py2app,
         # borg will fail unless Python environment is reset.
@@ -71,7 +73,7 @@
 
         try:
             return json.loads(line)
-        except:
+        except Exception as err:
             logger.warning('JSON parse failed on: "%s"' % line)
             return None
 
--- a/ui.py	Sun Jan 21 13:34:12 2018 +0000
+++ b/ui.py	Sun Jan 21 14:34:35 2018 +0000
@@ -30,6 +30,14 @@
 def combine_state(state1, state2):
     return max(state1, state2)
 
+# Workaround to rumps brokenness;
+# see https://github.com/jaredks/rumps/issues/59
+def notification_workaround(title, subtitle, message):
+    NSDictionary = objc.lookUpClass("NSDictionary")
+    d=NSDictionary()
+
+    rumps.notification(title, subtitle, message, data=d)
+
 # Based on code snatched from
 # https://stackoverflow.com/questions/12523586/python-format-size-application-converting-b-to-kb-mb-gb-tb/37423778
 def humanbytes(B):
@@ -169,14 +177,10 @@
             else:
                 msgid=errorlog['msgid']
 
-            logger.debug('Opening notification for error')
+            logger.debug("Opening notification for error %s '%s'",
+                         msgid, errorlog['message'])
 
-            # Workaround to rumps brokenness
-            # See https://github.com/jaredks/rumps/issues/59
-            NSDictionary = objc.lookUpClass("NSDictionary")
-            d=NSDictionary()
-
-            rumps.notification('Borgend', msgid, errorlog['message'], data=d)
+            notification_workaround('Borgend', msgid, errorlog['message'])
 
     def my_quit(self, _):
         rumps.quit_application()
@@ -184,12 +188,14 @@
     def __menu_select_backup(self, sender, b):
         #sender.state=not sender.state
         logger.debug("Manually backup '%s'", b.name)
-        b.create(None)
+        try:
+            b.create(None)
+        except Exception as err:
+            logger.debug("Opening notification for exception %s '%s'",
+                         err.__class__.__name__, str(err))
+            notification_workaround('Borgend', err.__class__.__name__, str(err))
 
     @rumps.notifications
     def notification_center(data):
         pass
 
-
-
-

mercurial