124 lines
3.8 KiB
Python
124 lines
3.8 KiB
Python
"""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/<tid>', 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/<tid>/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/<tid>/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))
|