diff --git a/app.py b/app.py index c6b8e17..cf988a2 100644 --- a/app.py +++ b/app.py @@ -9,6 +9,7 @@ import logging import threading import subprocess import time +import posixpath import uuid as _uuid_mod import urllib.request as _urlreq import urllib.error as _urlerr @@ -903,6 +904,33 @@ def _join_remote_path(*parts) -> str: return '/'.join(str(p).strip('/') for p in parts if str(p).strip('/')) +def _remote_exists(remote_path: str) -> bool: + r = _rclone('lsjson', remote_path, timeout=20) + if r.returncode != 0: + return False + try: + data = json.loads(r.stdout or '[]') + return bool(data) + except (json.JSONDecodeError, ValueError): + return bool(r.stdout.strip()) + + +def _remote_unique_rel_path(t: dict, rel_path: str) -> str: + if not _remote_exists(_smb_conn(t, rel_path)): + return rel_path + + parent = posixpath.dirname(rel_path) + name = posixpath.basename(rel_path) + stem, suffix = posixpath.splitext(name) + i = 1 + while True: + candidate_name = f'{stem}_({i}){suffix}' + candidate = _join_remote_path(parent, candidate_name) + if not _remote_exists(_smb_conn(t, candidate)): + return candidate + i += 1 + + def _smb_conn(t: dict, path: str = '') -> str: """Baut ein rclone-Ziel fuer gespeicherte SMB-Targets. @@ -989,9 +1017,12 @@ def run_uploads(local_dir: Path, cfg: dict): add_log(f'Upload >> {name}...') dest_root = t.get('dest_path', 'PiCopy').strip('/') root = _smb_conn(t) - dest = _smb_conn(t, dest_root) + # local_dir ist der lokal erzeugte Datumsordner. Auf dem NAS soll die + # gleiche Struktur entstehen wie auf dem Ziellaufwerk: Ziel/Datum/... + dest_rel = _join_remote_path(dest_root, local_dir.name) + dest = _smb_conn(t, dest_rel) share = t.get('smb_share', '') - dest_label = f'{share}/{dest_root}' if share and dest_root else (share or dest_root or '/') + dest_label = _join_remote_path(share, dest_rel) or '/' add_log(f'Upload {name}: Ziel {dest_label}') # Quellverzeichnis prüfen @@ -1020,11 +1051,36 @@ def run_uploads(local_dir: Path, cfg: dict): # 3. Kopieren add_log(f'Upload {name}: starte copy von {local_dir}') - r = _rclone('copy', str(local_dir), dest, - '--create-empty-src-dirs', - '--transfers', '1', - '--retries', '1', - timeout=7200) + dup_mode = cfg.get('duplicate_handling', 'skip') + if dup_mode == 'rename': + errors = [] + for f in sorted(local_dir.rglob('*')): + if not f.is_file(): + continue + rel = f.relative_to(local_dir).as_posix() + remote_rel = _remote_unique_rel_path(t, _join_remote_path(dest_rel, rel)) + rr = _rclone('copyto', str(f), _smb_conn(t, remote_rel), + '--retries', '1', timeout=7200) + if rr.returncode != 0: + errors.append(rr.stderr.strip() or f'{rel}: unbekannter Fehler') + if len(errors) >= 5: + break + r = subprocess.CompletedProcess( + args=['rclone', 'copyto'], + returncode=1 if errors else 0, + stdout='', + stderr='\n'.join(errors), + ) + else: + copy_args = [ + 'copy', str(local_dir), dest, + '--create-empty-src-dirs', + '--transfers', '1', + '--retries', '1', + ] + if dup_mode == 'skip': + copy_args.append('--size-only') + r = _rclone(*copy_args, timeout=7200) ok = r.returncode == 0 err = '' if not ok: diff --git a/version.txt b/version.txt index de0434d..c301979 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.0.47 \ No newline at end of file +1.0.48 \ No newline at end of file