Refactor code structure for improved readability and maintainability

This commit is contained in:
2026-05-13 12:01:11 +02:00
parent 50c0b4d012
commit f96c656385
22 changed files with 4352 additions and 4032 deletions

153
routes/copy_routes.py Normal file
View File

@@ -0,0 +1,153 @@
"""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)