backup.py

changeset 10
76dbfb06eba0
parent 8
7b2d2eac6a48
child 12
16a8c63344c0
--- a/backup.py	Sat Jan 20 15:55:09 2018 +0000
+++ b/backup.py	Sat Jan 20 19:57:05 2018 +0000
@@ -77,9 +77,9 @@
         self.thread_log=None
         self.thread_res=None
         self.timer=None
-        self.timer_operation=None
-        self.timer_time=None
+        self.scheduled_operation=None
         self.lock=Lock()
+        self.status_update_callback=None
 
     def is_running(self):
         with self.lock:
@@ -147,6 +147,11 @@
 
 
     def __result_listener(self):
+        with self.lock:
+            status, callback=self.__status_unlocked()
+        if callback:
+            callback(self, status)
+
         logging.debug('Result listener thread waiting for result')
 
         res=self.borg_instance.read_result()
@@ -165,22 +170,24 @@
         logging.debug('Borg subprocess terminated (success: %s); terminating result listener thread' % str(success))
 
         with self.lock:
-            if self.current_operation=='create':
-                self.lastrun=self.time_started
+            if self.current_operation['operation']=='create':
+                self.lastrun=self.current_operation['when_monotonic']
                 self.lastrun_success=success
             self.thread_res=None
             self.__finish_and_reschedule_if_both_listeners_terminated()
+            status, callback=self.__status_unlocked()
+        if callback:
+            callback(self, status)
 
     def __finish_and_reschedule_if_both_listeners_terminated(self):
         if self.thread_res==None and self.thread_log==None:
             logging.debug('Both threads terminated')
             self.borg_instance=None
-            self.time_started=None
             self.current_operation=None
             self.__schedule_unlocked()
 
-    def __do_launch(self, queue, operation, archive_or_repository, *args):
-        inst=BorgInstance(operation, archive_or_repository, *args)
+    def __do_launch(self, queue, op, archive_or_repository, *args):
+        inst=BorgInstance(op['operation'], archive_or_repository, *args)
         inst.launch()
 
         t_log=Thread(target=self.__log_listener)
@@ -193,13 +200,13 @@
         self.thread_res=t_res
         self.borg_instance=inst
         self.queue=queue
-        self.current_operation=operation
-        self.time_started=time.monotonic()
+        self.current_operation=op
+        self.current_operation['when_monotonic']=time.monotonic()
 
         t_log.start()
         t_res.start()
 
-    def __launch(self, operation, queue):
+    def __launch(self, op, queue):
         if self.__is_running_unlocked():
             logging.info('Cannot start %s: already running %s'
                          % (operation, self.current_operation))
@@ -208,38 +215,39 @@
             if self.timer:
                 logging.debug('Unscheduling timed operation due to launch of operation')
                 self.timer=None
-                self.timer_operation=None
-                self.timer_time=None
+                self.scheduled_operation=None
 
-            logging.debug("Launching '%s' on '%s'" % (operation, self.name))
+            logging.debug("Launching '%s' on '%s'" % (op['operation'], self.name))
 
-            if operation=='create':
+            if op['operation']=='create':
                 archive="%s::%s%s" % (self.repository,
                                       self.archive_prefix,
                                       self.archive_template)
 
-                self.__do_launch(queue, operation, archive,
+                self.__do_launch(queue, op, archive,
                                  self.common_parameters+self.create_parameters,
                                  self.paths)
-            elif operation=='prune':
-                self.__do_launch(queue, 'prune', self.repository,
+            elif op['operation']=='prune':
+                self.__do_launch(queue, op, self.repository,
                                  ([{'prefix': self.archive_prefix}] + 
                                   self.common_parameters +
                                   self.prune_parameters))
             else:
-                logging.error("Invalid operaton '%s'" % operation)
+                logging.error("Invalid operaton '%s'" % op['operation'])
                 self.__schedule_unlocked()
 
             return True
 
     def create(self, queue):
+        op={'operation': 'create', 'detail': 'manual'}
         with self.lock:
-            res=self.__launch('create', queue)
+            res=self.__launch(op, queue)
         return res
 
     def prune(self, queue):
+        op={'operation': 'prune', 'detail': 'manual'}
         with self.lock:
-            res=self.__launch('prune', queue)
+            res=self.__launch(op, queue)
         return res
 
     # TODO: Decide exact (manual) abort mechanism. Perhaps two stages
@@ -274,9 +282,8 @@
 
     def __queue_timed_operation(self):
         with self.lock:
-            operation=self.timer_operation
-            self.timer_operation=None
-            self.timer_time=None
+            op=self.scheduled_operation
+            self.scheduled_operation=None
             self.timer=None
 
             if self.__is_running_unlocked():
@@ -285,42 +292,85 @@
                 # TODO: Queue on 'repository' and online status for SSH, etc.
 
                 # TODO: UI comms. queue?
-                self.__launch(operation, None)
-
-    def __schedule_unlocked(self):
-        if self.current_operation:
-            return self.current_operation, None
-        else:
-            operation, when=self.__next_operation_unlocked()
-
-            if operation:
-                now=time.monotonic()
-                delay=max(0, when-now)
-                logging.info("Scheduling '%s' of '%s' in %d seconds" %
-                             (operation, self.name, delay))
-                tmr=Timer(delay, self.__queue_timed_operation)
-                self.timer_operation=operation
-                self.timer_time=when
-                self.timer=tmr
-                tmr.start()
-
-            return operation, time
+                self.__launch(op, None)
 
     def __next_operation_unlocked(self):
         # TODO: pruning as well
         now=time.monotonic()
         if not self.lastrun:
-            return 'create', now+self.retry_interval
+            initial_interval=self.retry_interval
+            if initial_interval==0:
+                initial_interval=self.backup_interval
+            if initial_interval==0:
+                return None
+            else:
+                return {'operation': 'create',
+                        'detail': 'initial',
+                        'when_monotonic': now+initial_interval}
         elif not self.lastrun_success:
-            return 'create', self.lastrun+self.retry_interval
+            if self.retry_interval==0:
+                return None
+            else:
+                return {'operation': 'create',
+                        'detail': 'retry',
+                        'when_monotonic': self.lastrun+self.retry_interval}
         else:
             if self.backup_interval==0:
-                return 'none', 0
+                return None
             else:
-                return 'create', self.lastrun+self.backup_interval
+                return {'operation': 'create',
+                        'detail': None,
+                        'when_monotonic': self.lastrun+self.backup_interval}
+
+    def __schedule_unlocked(self):
+        if self.current_operation:
+            return self.current_operation
+        else:
+            op=self.__next_operation_unlocked()
+
+            if op:
+                now=time.monotonic()
+                delay=max(0, op['when_monotonic']-now)
+                logging.info("Scheduling '%s' (detail: %s) of '%s' in %d seconds" %
+                             (op['operation'], op['detail'], self.name, delay))
+                tmr=Timer(delay, self.__queue_timed_operation)
+                self.scheduled_operation=op
+                self.timer=tmr
+                tmr.start()
+
+            return op
 
     def schedule(self):
         with self.lock:
             return self.__schedule_unlocked()
 
+    def set_status_update_callback(self, callback):
+        with self.lock:
+            self.status_update_callback=callback
 
+    def status(self):
+        with self.lock:
+            res=self.__status_unlocked()
+        return res[0]
+
+    def __status_unlocked(self):
+        callback=self.status_update_callback
+        if self.current_operation:
+            status=self.current_operation
+            status['type']='current'
+        elif self.scheduled_operation:
+            status=self.scheduled_operation
+            status['type']='scheduled'
+        else:
+            status={'type': 'nothing'}
+
+        status['name']=self.name
+
+        if 'when_monotonic' in status:
+            status['when']=(status['when_monotonic']
+                            -time.monotonic()+time.time())
+
+        return status, callback
+
+
+

mercurial