Files
PiCopy/routes/copy_routes.py

154 lines
4.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""PiCopy Blueprint: /api/copy/*, /api/devices, /api/storage-info, /api/status, /api/config*."""
import shutil
import subprocess
import threading
from flask import Blueprint, jsonify, request
from picopy.config import load_cfg, save_cfg, _fmt_bytes
from picopy.state import copy_state, copy_lock
from picopy.usb import usb_devices, ensure_mount, internal_dest_device
from picopy.wifi import wifi_state, wifi_lock
from picopy.wireguard import wg_state, wg_lock
from picopy.samba import internal_share_update_state
import picopy.copy_engine as _ce
copy_bp = Blueprint('copy', __name__)
def _resolve_source_ports(cfg) -> list:
ports = cfg.get('source_ports') or []
if not ports and cfg.get('source_port'):
ports = [{'port': cfg['source_port'], 'label': cfg.get('source_label', '')}]
return ports
def _configured_destination(cfg, devs):
if cfg.get('dest_type') == 'internal':
return internal_dest_device(cfg)
return next((d for d in devs if d['usb_port'] == cfg.get('dest_port')), None)
@copy_bp.route('/api/devices')
def r_devices():
return jsonify(usb_devices())
@copy_bp.route('/api/storage-info')
def r_storage_info():
cfg = load_cfg()
devs = usb_devices()
result = []
src_ports = {sp['port'] for sp in _resolve_source_ports(cfg)}
dst_port = cfg.get('dest_port')
def _du_for_dev(dev):
mp, owned = ensure_mount(dev)
if not mp:
return dict(total=None, used=None, free=None, pct=None)
try:
du = shutil.disk_usage(mp)
return dict(total=du.total, used=du.used, free=du.free,
pct=round(du.used / du.total * 100) if du.total else 0)
except Exception:
return dict(total=None, used=None, free=None, pct=None)
finally:
if owned:
subprocess.run(['umount', mp], capture_output=True)
for dev in devs:
port = dev['usb_port']
if port in src_ports:
role = 'source'
elif port == dst_port:
role = 'dest'
else:
role = 'other'
entry = dict(
role=role,
label=dev.get('label') or dev.get('device') or f'Port {port}',
port=port,
device=dev.get('device', ''),
size_str=dev.get('size', ''),
)
entry.update(_du_for_dev(dev))
result.append(entry)
if cfg.get('dest_type') == 'internal':
entry = dict(role='dest',
label=cfg.get('internal_dest_label') or 'Interner Speicher',
port='__internal__', device='internal', size_str='')
entry.update(_du_for_dev({'internal': True}))
result.append(entry)
return jsonify(result)
@copy_bp.route('/api/config', methods=['GET', 'POST'])
def r_config():
if request.method == 'POST':
cfg = load_cfg()
cfg.update(request.get_json(force=True))
save_cfg(cfg)
return jsonify(ok=True)
return jsonify(load_cfg())
@copy_bp.route('/api/config/ports/reset', methods=['POST'])
def r_ports_reset():
cfg = load_cfg()
cfg['source_ports'] = []
cfg['source_port'] = None
cfg['source_label'] = ''
cfg['dest_port'] = None
cfg['dest_label'] = ''
cfg['dest_type'] = 'usb'
save_cfg(cfg)
return jsonify(ok=True)
@copy_bp.route('/api/status')
def r_status():
with copy_lock:
cs = dict(copy_state)
with wifi_lock:
ws = dict(wifi_state)
with wg_lock:
wgs = dict(wg_state)
share = internal_share_update_state()
return jsonify(copy=cs, wifi=ws, vpn=wgs, internal_share=share)
@copy_bp.route('/api/copy/start', methods=['POST'])
def r_start():
with copy_lock:
if copy_state['running']:
return jsonify(error='Bereits aktiv'), 400
if _ce._copy_thread is not None and _ce._copy_thread.is_alive():
return jsonify(error='Abbruch wird noch abgeschlossen - bitte kurz warten und erneut versuchen.'), 400
cfg = load_cfg()
devs = usb_devices()
body = request.get_json(force=True) or {}
wanted_ports = body.get('ports') # None = alle konfigurierten Quellen
src_ports = _resolve_source_ports(cfg)
srcs = [next((d for d in devs if d['usb_port'] == sp['port']), None) for sp in src_ports]
srcs = [s for s in srcs if s is not None]
if wanted_ports is not None:
srcs = [s for s in srcs if s['usb_port'] in wanted_ports]
if not srcs: return jsonify(error='Keine Quellgeräte gefunden (Ports nicht verbunden)'), 400
dst = _configured_destination(cfg, devs)
if not dst: return jsonify(error='Zielgerät nicht gefunden'), 400
t = threading.Thread(target=_ce.do_copy, args=(srcs, dst, cfg), daemon=True)
_ce._copy_thread = t
t.start()
return jsonify(ok=True)
@copy_bp.route('/api/copy/cancel', methods=['POST'])
def r_cancel():
with copy_lock:
copy_state['running'] = False
return jsonify(ok=True)