Implemented transaction based rsync for single remote server

Change-Id: I0658e11be22dd3fd950ff3e8935dd95541e1ebd0
diff --git a/rsync_versioned.py b/rsync_versioned.py
index 12d2828..802c9b5 100644
--- a/rsync_versioned.py
+++ b/rsync_versioned.py
@@ -67,10 +67,78 @@
         extra = '--link-dest={}'.format(
             self.url.a_file(self.url.path, latest_path)
         )
-        result = super(RsyncVersioned, self).push(source, repo_path, extra)
-        self.symlink(repo_name, repo_path)
-        self.symlink(latest_path, snapshot_name)
-        self._remove_old_snapshots(repo_name)
+        # TODO: retry on base class!!!!!!!!!!!!!!!
+        # TODO: locking - symlink dir-timestamp.lock -> dir-timestamp
+        # TODO: write yaml file with symlink info
+        transaction = list()
+        try:
+            # start transaction
+            result = super(RsyncVersioned, self).push(source, repo_path, extra)
+            transaction.append('repo_dir_created')
+            self.logger.info('{}'.format(result))
+
+            try:
+                old_repo_name_symlink_target = \
+                    [_[1] for _ in self.ls_symlinks(repo_name)][0]
+                self.logger.info('Previous {} -> {}'
+                                 ''.format(repo_name,
+                                           old_repo_name_symlink_target))
+                status = 'updated'
+            except:
+                status = 'created'
+            self.symlink(repo_name, repo_path)
+            transaction.append('symlink_repo_name_{}'.format(status))
+
+            try:
+                old_latest_path_symlink_target = \
+                    [_[1] for _ in self.ls_symlinks(latest_path)][0]
+                self.logger.info('Previous {} -> {}'
+                                 ''.format(latest_path,
+                                           old_latest_path_symlink_target))
+                status = 'updated'
+            except:
+                status = 'created'
+            self.symlink(latest_path, snapshot_name)
+            transaction.append('symlink_latest_path_{}'.format(status))
+
+            self._remove_old_snapshots(repo_name)
+            transaction.append('old_snapshots_deleted')
+
+        except RuntimeError as e:
+            #self.logger.error(e.message)
+            # deleting of old snapshots ignored when assessing the transaction
+            # only warning
+            if 'old_snapshots_deleted' not in transaction:
+                self.logger.warn("Old snapshots are not deleted. Ignore. "
+                                 "May be next time.")
+                transaction.append('old_snapshots_deleted')
+
+            if len(transaction) < 4:
+                # rollback transaction if some of sync operations failed
+
+                if 'symlink_latest_path_updated' in transaction:
+                    self.logger.info('Restoring symlink {} -> {}'
+                                     ''.format(latest_path,
+                                               old_latest_path_symlink_target))
+                    self.symlink(latest_path, old_latest_path_symlink_target)
+                elif 'symlink_latest_path_created' in transaction:
+                    self.logger.info('Deleting symlink {}'.format(latest_path))
+                    self.rmfile(latest_path)
+
+                if 'symlink_repo_name_updated' in transaction:
+                    self.logger.info('Restoring symlink {} -> {}'
+                                     ''.format(repo_name,
+                                               old_repo_name_symlink_target))
+                    self.symlink(repo_name, old_repo_name_symlink_target)
+                elif 'symlink_repo_name_created' in transaction:
+                    self.logger.info('Deleting symlink {}'.format(repo_name))
+                    self.rmfile(repo_name)
+
+                if 'repo_dir_created' in transaction:
+                    self.logger.info('Removing snapshot {}'.format(repo_path))
+                    self.rmdir(repo_path)
+                raise
+
         return result
 
     def _remove_old_snapshots(self, repo_name, save_latest_days=None):