feat: Unterstützung für Duplikatbehandlung beim Upload hinzugefügt; Versionsnummer auf 1.0.48 erhöht
This commit is contained in:
64
app.py
64
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,
|
||||
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',
|
||||
timeout=7200)
|
||||
]
|
||||
if dup_mode == 'skip':
|
||||
copy_args.append('--size-only')
|
||||
r = _rclone(*copy_args, timeout=7200)
|
||||
ok = r.returncode == 0
|
||||
err = ''
|
||||
if not ok:
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.0.47
|
||||
1.0.48
|
||||
Reference in New Issue
Block a user