#!/usr/bin/python3 import os import re import subprocess import humanfriendly from .structures import Progress class Rsync: _rsync_progress_re = re.compile(r'^\s*(\S+)\s+(\d+)%\s+(\S+)\s+(\S+)\s+$') def __init__(self, src, dst): self.proc = subprocess.Popen( [ 'rsync', '--partial', # not human readable, easier to parse (but speed still appears # in human form). '--no-h', '--info=progress2', src, dst, ], stdout=subprocess.PIPE, #stderr=subprocess.DEVNULL, encoding='utf8', env={**os.environ, **{'LC_ALL':'C.UTF-8', 'LANG':'C.UTF-8', 'LANGUAGE':'C.UTF-8', }}, ) self.last_progress = Progress() def select_fd(self): 'useful to use selectors with the process stdout file descriptor' return self.proc.stdout def progress_read(self): line = self.proc.stdout.readline() if match := self._rsync_progress_re.search(line): self.last_progress = Progress( int(match.group(1)), int(match.group(2)), humanfriendly.parse_size(match.group(3), binary=True), match.group(4), ) return self.last_progress def terminate(self): self.proc.terminate() def poll(self): 'returns None if not terminated, otherwise return returncode' return self.proc.poll() def wait(self, timeout=None): return self.proc.wait(timeout) def __enter__(self): return self def __exit__(self, exc_type, value, traceback): self.terminate() self.wait() if __name__ == '__main__': import sys import contextlib with contextlib.suppress(KeyboardInterrupt): with Rsync(sys.argv[1], sys.argv[2]) as rsync: while rsync.poll() is None: progress = rsync.progress_read() print(f'{progress.bytes}b {progress.percent}% ' f'{progress.speed}b/s {progress.eta}') rc = rsync.poll() print(f'rsync ended with code: {rc}') print('Rsync test main ended')