feat: Unterstützung für Duplikatbehandlung beim Upload hinzugefügt; Versionsnummer auf 1.0.48 erhöht

This commit is contained in:
2026-05-09 14:01:38 +02:00
parent 5ecea9fc21
commit 52081ad8f0
2 changed files with 64 additions and 8 deletions

64
app.py
View File

@@ -9,6 +9,7 @@ import logging
import threading import threading
import subprocess import subprocess
import time import time
import posixpath
import uuid as _uuid_mod import uuid as _uuid_mod
import urllib.request as _urlreq import urllib.request as _urlreq
import urllib.error as _urlerr 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('/')) 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: def _smb_conn(t: dict, path: str = '') -> str:
"""Baut ein rclone-Ziel fuer gespeicherte SMB-Targets. """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}...') add_log(f'Upload >> {name}...')
dest_root = t.get('dest_path', 'PiCopy').strip('/') dest_root = t.get('dest_path', 'PiCopy').strip('/')
root = _smb_conn(t) 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', '') 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}') add_log(f'Upload {name}: Ziel {dest_label}')
# Quellverzeichnis prüfen # Quellverzeichnis prüfen
@@ -1020,11 +1051,36 @@ def run_uploads(local_dir: Path, cfg: dict):
# 3. Kopieren # 3. Kopieren
add_log(f'Upload {name}: starte copy von {local_dir}') add_log(f'Upload {name}: starte copy von {local_dir}')
r = _rclone('copy', str(local_dir), dest, 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', '--create-empty-src-dirs',
'--transfers', '1', '--transfers', '1',
'--retries', '1', '--retries', '1',
timeout=7200) ]
if dup_mode == 'skip':
copy_args.append('--size-only')
r = _rclone(*copy_args, timeout=7200)
ok = r.returncode == 0 ok = r.returncode == 0
err = '' err = ''
if not ok: if not ok:

View File

@@ -1 +1 @@
1.0.47 1.0.48