feat: Datei-Manager Multi-Select + Bulk-Download (ZIP) + Bulk-Delete
Diagnostic + App bekommen Mehrfach-Auswahl im Datei-Manager. Mehr als eine
Datei ausgewaehlt → Download als ZIP. Genau eine ausgewaehlt → einzeln.
Bulk-Delete loescht alle markierten in einem Rutsch.
diagnostic/Dockerfile
zip via apk add — fuer das ZIP-Streaming im /api/files-download-zip.
diagnostic/server.js
POST /api/files-download-zip Body: {paths:[...]} → spawnt 'zip -j -q -',
Pipes stdout in Response. Whitelist auf
/shared/uploads/.
POST /api/files-delete-batch Body: {paths:[...]} → loescht alle, broadcastet
file_deleted pro Pfad an Browser + RVS.
diagnostic/index.html
filesSelected Set + Checkbox-UI pro Datei + "Alle markieren". Wenn 2+
ausgewaehlt: POST an /api/files-download-zip, Browser saugt das als
Blob runter. Bei 1: normaler Single-Download.
bridge/aria_bridge.py
file_delete_batch_request → ruft Diagnostic /api/files-delete-batch,
antwortet mit file_delete_batch_response.
file_zip_request {paths,reqId} → ruft Diagnostic /api/files-download-zip,
base64-kodiert, capped auf 30 MB,
sendet file_zip_response.
rvs/server.js
ALLOWED_TYPES: file_delete_batch_request/response, file_zip_request/response.
android/src/screens/SettingsScreen.tsx
fileManagerSelected Set + Checkbox-UI pro Datei + "Alle markieren"-Zeile
oben. Bulk-Bar oben mit count, "⬇ ZIP" / "⬇ Download" (je nach Anzahl),
und "🗑 Löschen". ZIP-Response landet base64 → RNFS in Downloads-Folder
(aria-files-<timestamp>.zip), Toast mit Pfad.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1761,6 +1761,89 @@ class ARIABridge:
|
||||
logger.warning("[rvs] file_list_request: %s", e)
|
||||
return
|
||||
|
||||
elif msg_type == "file_delete_batch_request":
|
||||
# App will mehrere Dateien auf einmal loeschen.
|
||||
paths = payload.get("paths") or []
|
||||
req_id = payload.get("requestId", "")
|
||||
logger.warning("[rvs] file_delete_batch_request: %d Pfade", len(paths))
|
||||
try:
|
||||
body_bytes = json.dumps({"paths": paths}).encode("utf-8")
|
||||
req = urllib.request.Request(
|
||||
"http://localhost:3001/api/files-delete-batch",
|
||||
data=body_bytes, method="POST",
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
def _do_delete():
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
return resp.status, resp.read().decode("utf-8", errors="ignore")
|
||||
except Exception as e:
|
||||
return None, str(e)
|
||||
status, body = await asyncio.get_event_loop().run_in_executor(None, _do_delete)
|
||||
logger.info("[rvs] file_delete_batch result: status=%s", status)
|
||||
# Server broadcastet file_deleted pro Pfad — App kriegt das via persistente RVS.
|
||||
# Wir bestaetigen zusaetzlich mit Counts.
|
||||
try: d = json.loads(body or "{}")
|
||||
except: d = {}
|
||||
await self._send_to_rvs({
|
||||
"type": "file_delete_batch_response",
|
||||
"payload": {
|
||||
"requestId": req_id,
|
||||
"deleted": len(d.get("deleted", [])),
|
||||
"errors": d.get("errors", []),
|
||||
},
|
||||
"timestamp": int(asyncio.get_event_loop().time() * 1000),
|
||||
})
|
||||
except Exception as e:
|
||||
logger.warning("[rvs] file_delete_batch_request: %s", e)
|
||||
return
|
||||
|
||||
elif msg_type == "file_zip_request":
|
||||
# App will mehrere Dateien als ZIP. Bridge holt ZIP von Diagnostic
|
||||
# via HTTP, kodiert base64 und schickt zurueck. Cap auf 30 MB
|
||||
# ZIP-Groesse damit RVS nicht erstickt.
|
||||
paths = payload.get("paths") or []
|
||||
req_id = payload.get("requestId", "")
|
||||
logger.warning("[rvs] file_zip_request: %d Pfade (req=%s)", len(paths), req_id)
|
||||
|
||||
def _do_zip():
|
||||
try:
|
||||
body_bytes = json.dumps({"paths": paths}).encode("utf-8")
|
||||
req = urllib.request.Request(
|
||||
"http://localhost:3001/api/files-download-zip",
|
||||
data=body_bytes, method="POST",
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=120) as resp:
|
||||
if resp.status != 200:
|
||||
return None, f"HTTP {resp.status}"
|
||||
data = resp.read()
|
||||
if len(data) > 30 * 1024 * 1024:
|
||||
return None, f"ZIP zu gross ({len(data) // (1024*1024)} MB > 30 MB)"
|
||||
return data, None
|
||||
except Exception as e:
|
||||
return None, str(e)
|
||||
|
||||
data, err = await asyncio.get_event_loop().run_in_executor(None, _do_zip)
|
||||
if err or data is None:
|
||||
await self._send_to_rvs({
|
||||
"type": "file_zip_response",
|
||||
"payload": {"requestId": req_id, "ok": False, "error": err or "leer"},
|
||||
"timestamp": int(asyncio.get_event_loop().time() * 1000),
|
||||
})
|
||||
return
|
||||
import base64 as _b64
|
||||
await self._send_to_rvs({
|
||||
"type": "file_zip_response",
|
||||
"payload": {
|
||||
"requestId": req_id, "ok": True,
|
||||
"size": len(data),
|
||||
"data": _b64.b64encode(data).decode("ascii"),
|
||||
},
|
||||
"timestamp": int(asyncio.get_event_loop().time() * 1000),
|
||||
})
|
||||
return
|
||||
|
||||
elif msg_type == "file_delete_request":
|
||||
# App will eine Datei loeschen — leite an Diagnostic.
|
||||
p = payload.get("path", "")
|
||||
|
||||
Reference in New Issue
Block a user