Добавлен прогресс бар при синхронизации

main
ilyukhin 2 months ago
parent 10d2e0d752
commit 0fda00ba85

81
dsync

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
VERSION = "0.3.0" VERSION = "0.4.0"
import re import re
import sys import sys
@ -25,6 +25,10 @@ class Colors:
BOLD = '\033[1m' BOLD = '\033[1m'
# Simple inline progress bar renderer
_BAR_WIDTH = 28
def colored(text: str, color: str) -> str: def colored(text: str, color: str) -> str:
"""Wrap text with an ANSI color. """Wrap text with an ANSI color.
@ -359,6 +363,66 @@ def parse_rsync_action(line: str) -> str:
return f"{direction} {item_type.upper()}: {filename}" if not details \ return f"{direction} {item_type.upper()}: {filename}" if not details \
else f"{direction} {item_type.upper()}: {filename} ({', '.join(details)})" else f"{direction} {item_type.upper()}: {filename} ({', '.join(details)})"
def _render_progress(prefix: str, pct: int) -> None:
"""Render a single-line progress bar like: '[#####.....] 42%'.
Args:
prefix: Text shown before the bar (e.g., node label).
pct: Integer percent [0..100].
"""
pct = max(0, min(100, pct))
filled = int(_BAR_WIDTH * pct / 100)
bar = "#" * filled + "." * (_BAR_WIDTH - filled)
end = "\r" if sys.stdout.isatty() else "\n"
print(f"{prefix} [{bar}] {pct:3d}% ", end=end, flush=True)
def _run_rsync_with_progress(cmd: List[str], label: str) -> None:
"""Execute rsync and show a best-effort progress bar.
Notes:
- Removes '-v' for cleaner output and appends '--info=progress2'.
- Parses percentage from rsync output and updates a single-line bar.
- Forces a final 100% on success when rsync emits no/low progress.
"""
base = [c for c in cmd if c != "-v"]
if "--info=progress2" not in base:
base.append("--info=progress2")
proc = subprocess.Popen(
base,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
)
last_pct = -1
try:
assert proc.stdout is not None
for line in proc.stdout:
m = re.search(r"(\d+)%", line)
if m:
pct = int(m.group(1))
if pct != last_pct:
_render_progress(label, pct)
last_pct = pct
rc = proc.wait()
# NEW: if rsync completed successfully but never reached 100% (or printed nothing),
# render a final 100% so the user doesn't see a stuck 0%.
if rc == 0 and last_pct != 100:
_render_progress(label, 100)
if sys.stdout.isatty():
print() # finalize the progress line
if rc != 0:
raise subprocess.CalledProcessError(rc, base)
finally:
if proc.stdout:
proc.stdout.close()
def sync(reverse: bool = False) -> None: def sync(reverse: bool = False) -> None:
"""Synchronize local project directory to all nodes. """Synchronize local project directory to all nodes.
@ -387,23 +451,22 @@ def sync(reverse: bool = False) -> None:
node = n["name"] node = n["name"]
rdir = n["remote_dir"] or master_dir rdir = n["remote_dir"] or master_dir
remote = f"{node}:{rdir}/" # trailing slash to copy dir contents remote = f"{node}:{rdir}/" # trailing slash to copy dir contents
print(f"[Reverse] From {remote} -> {project_dir}/ (ignore-existing)") label = f"[Reverse] {node} -> master"
# --ignore-existing ensures we only fetch files that do NOT exist locally.
pull_cmd = [ pull_cmd = [
"rsync", "-avz", "--ignore-existing", "rsync", "-az", "--ignore-existing", # dropped '-v' for cleaner progress
f"--exclude-from={ignore_file}", f"--exclude-from={ignore_file}",
"-e", "ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10", "-e", "ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10",
remote, f"{project_dir}/" remote, f"{project_dir}/"
] ]
try: try:
subprocess.run(pull_cmd, check=True, capture_output=True, text=True) _run_rsync_with_progress(pull_cmd, label)
print(colored(f"[OK] Pulled missing files from {node}", Colors.GREEN)) print(colored(f"[OK] Pulled missing files from {node}", Colors.GREEN))
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print(colored(f"[X] Failed reverse-pull from {node} ({rdir}): {e}", Colors.RED)) print(colored(f"[X] Failed reverse-pull from {node} ({rdir}): {e}", Colors.RED))
print() # spacing print() # spacing
# --- Forward phase: push master to all nodes (same as before) --- # --- Forward phase: push master to all nodes ---
success_count = 0 success_count = 0
failed_nodes: List[str] = [] failed_nodes: List[str] = []
@ -411,16 +474,16 @@ def sync(reverse: bool = False) -> None:
node = n["name"] node = n["name"]
rdir = n["remote_dir"] or master_dir rdir = n["remote_dir"] or master_dir
remote = f"{node}:{rdir}" remote = f"{node}:{rdir}"
print(f"Syncing to {remote}...") label = f"[Sync] {node}"
cmd = [ cmd = [
"rsync", "-avz", "--delete", "rsync", "-az", "--delete", # dropped '-v' for cleaner progress
f"--exclude-from={ignore_file}", f"--exclude-from={ignore_file}",
"-e", "ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10", "-e", "ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10",
f"{project_dir}/", remote f"{project_dir}/", remote
] ]
try: try:
subprocess.run(cmd, check=True, capture_output=True, text=True) _run_rsync_with_progress(cmd, label)
print(colored(f"[OK] Successfully synced to {node} ({rdir})", Colors.GREEN)) print(colored(f"[OK] Successfully synced to {node} ({rdir})", Colors.GREEN))
success_count += 1 success_count += 1
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:

Loading…
Cancel
Save