"""PiCopy – Blueprint: /api/upload/*.""" import subprocess import uuid as _uuid_mod from flask import Blueprint, jsonify, request from picopy.config import load_cfg, save_cfg from picopy.upload import ( upload_state, upload_lock, configure_smb_remote, delete_remote, test_remote, _rclone_obscure, RCLONE_CONF as _RCLONE_CONF, ) upload_bp = Blueprint('upload', __name__) @upload_bp.route('/api/upload/targets', methods=['GET']) def r_upload_list(): return jsonify(load_cfg().get('upload_targets', [])) @upload_bp.route('/api/upload/targets', methods=['POST']) def r_upload_add(): data = request.get_json(force=True) cfg = load_cfg() tid = data.get('id') or _uuid_mod.uuid4().hex[:8] ctype = data.get('type', 'smb') if ctype != 'smb': return jsonify(error='Nur SMB/NAS wird unterstützt'), 400 ok, err = configure_smb_remote( tid, data.get('host', ''), data.get('share', ''), data.get('user', ''), data.get('pass', '')) if not ok: return jsonify(error=f'rclone: {err}'), 500 # Credentials direkt im Entry speichern (für Connection-String bei Upload) obscured_pw = _rclone_obscure(data.get('pass', '')) if data.get('pass') else '' entry = { 'id': tid, 'type': ctype, 'name': data.get('name', ctype), 'dest_path': data.get('dest_path', 'PiCopy'), 'enabled': True, 'smb_host': data.get('host', ''), 'smb_share': data.get('share', ''), 'smb_user': data.get('user', ''), 'smb_pass': obscured_pw, } targets = [t for t in cfg.get('upload_targets', []) if t['id'] != tid] targets.append(entry) cfg['upload_targets'] = targets save_cfg(cfg) return jsonify(ok=True, id=tid) @upload_bp.route('/api/upload/targets/', methods=['DELETE']) def r_upload_del(tid): cfg = load_cfg() cfg['upload_targets'] = [t for t in cfg.get('upload_targets', []) if t['id'] != tid] save_cfg(cfg) delete_remote(tid) return jsonify(ok=True) @upload_bp.route('/api/upload/browse', methods=['POST']) def r_upload_browse(): """Listet SMB-Freigaben eines Servers ohne gespeicherte Config (rclone connection string).""" data = request.get_json(force=True) host = data.get('host', '').strip() user = data.get('user', '').strip() pw = data.get('pass', '') if not host: return jsonify(error='Server-Adresse fehlt'), 400 conn = f':smb,host={host}' if user: conn += f',user={user}' if pw: try: obscured = _rclone_obscure(pw) conn += f',pass={obscured}' except Exception: pass conn += ':' r = subprocess.run( ['rclone', '--config', str(_RCLONE_CONF), 'lsd', conn], capture_output=True, text=True, timeout=15 ) if r.returncode != 0: lines = r.stderr.strip().splitlines() err = lines[-1] if lines else 'Verbindung fehlgeschlagen' return jsonify(error=err), 400 shares = [line.strip().split()[-1] for line in r.stdout.splitlines() if line.strip()] return jsonify(shares=shares) @upload_bp.route('/api/upload/targets//toggle', methods=['POST']) def r_upload_toggle(tid): cfg = load_cfg() for t in cfg.get('upload_targets', []): if t['id'] == tid: t['enabled'] = not t.get('enabled', True) break save_cfg(cfg) return jsonify(ok=True) @upload_bp.route('/api/upload/targets//test', methods=['POST']) def r_upload_test(tid): from picopy.config import log try: ok, err = test_remote(tid) except Exception as e: log.exception('upload test failed') ok, err = False, str(e) return jsonify(ok=ok, error=err) @upload_bp.route('/api/upload/status') def r_upload_status(): with upload_lock: return jsonify(dict(upload_state))