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 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:
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.0.47
|
1.0.48
|
||||||
Reference in New Issue
Block a user