rsync.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 
00003 # Software License Agreement (BSD License)
00004 #
00005 # Copyright (c) 2016, Alex McClung
00006 # All rights reserved.
00007 #
00008 # Redistribution and use in source and binary forms, with or without
00009 # modification, are permitted provided that the following conditions
00010 # are met:
00011 #
00012 #  * Redistributions of source code must retain the above copyright
00013 #    notice, this list of conditions and the following disclaimer.
00014 #  * Redistributions in binary form must reproduce the above
00015 #    copyright notice, this list of conditions and the following
00016 #    disclaimer in the documentation and/or other materials provided
00017 #    with the distribution.
00018 #  * Neither the name of the author may be used to endorse or promote 
00019 #    products derived from this software without specific prior
00020 #    written permission.
00021 #
00022 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00023 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00024 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00025 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00026 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00027 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00028 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00029 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00030 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00031 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00032 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00033 # POSSIBILITY OF SUCH DAMAGE.
00034 
00035 import re
00036 from subprocess import Popen, PIPE
00037 
00038 class Rsync():
00039 
00040     def __init__(self, rsync_args, source, dest, progress_callback=None):
00041         self.rsync_args = rsync_args
00042         self.source = source
00043         self.dest = dest
00044         self.percent_complete = 0
00045         self.progress_callback = progress_callback
00046         self.transfer_rate = 0
00047 
00048         self.stdout_block = ''
00049         self.stderr_block = ''
00050     
00051     def sync(self):
00052         #Sync the files
00053         self.rsync_cmd = ['rsync'] + self.rsync_args + ['--progress', '--outbuf=L', self.source, self.dest]
00054         self.p = Popen(self.rsync_cmd, stdout=PIPE, stderr=PIPE)
00055         
00056         #Catch stdout from RSync in (near) real-time
00057         for line_with_whitespace in iter(self.p.stdout.readline, b''):
00058             self.line = re.sub( '\s+', ' ', line_with_whitespace).strip() #Remove excess whitespace
00059             self.stdout_block += self.line
00060 
00061             #Calculate percentage by parsing the stdout line
00062             if self.progress_callback:
00063                 self._parse_progress()
00064                 self._parse_transfer_rate()
00065                 self.progress_callback(self.line, self.percent_complete, self.transfer_rate)
00066 
00067         self.stderr_block = '\n'.join(self.p.stderr)
00068 
00069         self.p.poll()
00070 
00071         if self.p.returncode > -1:
00072             #Set feedback to 100% complete, for cases when no progress is piped from Rsync stdout
00073             self.progress_callback(None, 100.0, self.transfer_rate)
00074             return True
00075         else:
00076             return False
00077 
00078     def _parse_progress(self):
00079         #Parse line of stdout. If regex matches, calculate the Sync Progress percentage.
00080         re_matches = re.findall(r'(to-chk|to-check)=(\d+)/(\d+)', self.line) #e.g. ('to-chk', remaining_files, total_files)
00081         if re_matches:
00082             progress_tuple = re_matches[0]
00083             self.remaining_files = float(progress_tuple[1])
00084             self.total_files = float(progress_tuple[2])
00085             self.percent_complete = round(100.0 * (1 - (self.remaining_files/self.total_files)), 2)
00086 
00087     def _parse_transfer_rate(self):
00088         smoothing_effect = 0.1 #0 < smoothing_effect <= 1, 1 implies no smoothing on output
00089 
00090         rate_tuples = re.findall(r'(\s[0-9]*\.[0-9]+|[0-9]+)(kb|mb|gb|tb)\/s\s', self.line.lower()) #e.g. (1000, 'MB') implies 1000MB/s in tuple format
00091         rate_conversions = {'kb':3, 'mb':6, 'gb':9, 'tb':12} #rate_tuple * pow(10, value) = bytes/sec
00092         
00093         if rate_tuples:
00094             if rate_tuples[-1][1] in rate_conversions:
00095                 rate_tuple = rate_tuples[-1]
00096                 transfer_rate_sample = float(rate_tuple[0]) * pow(10, rate_conversions[rate_tuple[1]])
00097                 
00098                 #Smoothing Function
00099                 self.transfer_rate = smoothing_effect * transfer_rate_sample + (1.0-smoothing_effect) * self.transfer_rate
00100 
00101     def get_progress(self):
00102         return self.percent_complete


rsync_ros
Author(s):
autogenerated on Thu Jul 21 2016 06:13:41