feat(app): Versions-Historie pro Datei im App-Datei-Manager
Step 3 vom File-Versioning-Feature (Step 1+2 lief schon in Diagnostic). App kann jetzt: - pro Datei via 🕒-Button die Versions-Liste anzeigen - alte Versionen als '<name>@<short-hash>.<ext>' nach Downloads schreiben - per ⟲ eine alte Version als neue aktive setzen (non-destructive, macht im Backend einen 'restore:'-Commit, die bisherige Version bleibt in der Historie) Drei neue RVS-Message-Type-Paare: file_version_list_request / _response file_version_download_request / _response (base64) file_version_restore_request / _response rvs/server.js: alle sechs Typen in die ALLOWED_TYPES-Whitelist. bridge/aria_bridge.py: handler proxen die Anfragen an diagnostic (http://localhost:3001/api/files-versions / -version-content / -restore). Diagnostic ist eh schon der Owner der git-Repository-Logik. Bridge wrappt die Binary-Antwort als base64 fuer den RVS-Transport. android/src/screens/SettingsScreen.tsx: - State versionsOpen/versionsList/-Loading/-Error - Drei rvs.onMessage-Branches fuer die neuen *_response Types - 🕒-Button in jeder Datei-Zeile (zwischen Auswahl-Checkbox und Mülltonne) - Neues Modal mit Versions-Liste (AKTIV-Badge, short-hash, subject, formatiertes Datum, ⬇ Download + ⟲ Restore-Button pro Eintrag) - Restore-Button hat Confirm-Alert - Bei file_version_restore_response: list refresh + file-list refresh
This commit is contained in:
@@ -2405,6 +2405,129 @@ class ARIABridge:
|
||||
logger.warning("[rvs] file_delete_request: %s", e)
|
||||
return
|
||||
|
||||
elif msg_type == "file_version_list_request":
|
||||
# Versions-Historie einer Datei (App-Side Dateimanager).
|
||||
# Pfad ist relativ-zu-/shared/uploads, kommt vom App-File-Manager
|
||||
# der eh nur diesen flachen Bereich anzeigt. Diagnostic hat die
|
||||
# git-Logik — wir proxien.
|
||||
req_path = payload.get("path", "")
|
||||
logger.info("[rvs] file_version_list_request: %s", req_path)
|
||||
try:
|
||||
qs = urllib.parse.urlencode({"path": req_path})
|
||||
req = urllib.request.Request(
|
||||
f"http://localhost:3001/api/files-versions?{qs}",
|
||||
method="GET",
|
||||
)
|
||||
def _do_list():
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
return json.loads(resp.read().decode("utf-8", errors="ignore"))
|
||||
except Exception as e:
|
||||
return {"ok": False, "error": str(e)}
|
||||
d = await asyncio.get_event_loop().run_in_executor(None, _do_list)
|
||||
await self._send_to_rvs({
|
||||
"type": "file_version_list_response",
|
||||
"payload": d,
|
||||
"timestamp": int(asyncio.get_event_loop().time() * 1000),
|
||||
})
|
||||
except Exception as e:
|
||||
logger.warning("[rvs] file_version_list_request: %s", e)
|
||||
return
|
||||
|
||||
elif msg_type == "file_version_download_request":
|
||||
# Inhalt einer alten Version holen, base64 zurueck. Diagnostic
|
||||
# liefert Binary, wir wrappen als base64 in der Response damit
|
||||
# die App's RVS-WS damit umgehen kann.
|
||||
req_path = payload.get("path", "")
|
||||
req_hash = payload.get("hash", "")
|
||||
req_id = payload.get("requestId", "")
|
||||
logger.info("[rvs] file_version_download_request: %s @ %s",
|
||||
req_path, req_hash[:7])
|
||||
try:
|
||||
qs = urllib.parse.urlencode({"path": req_path, "hash": req_hash})
|
||||
req = urllib.request.Request(
|
||||
f"http://localhost:3001/api/files-version-content?{qs}",
|
||||
method="GET",
|
||||
)
|
||||
def _do_dl():
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
return resp.status, resp.read()
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, e.read()
|
||||
except Exception as e:
|
||||
return None, str(e).encode("utf-8")
|
||||
status, body = await asyncio.get_event_loop().run_in_executor(None, _do_dl)
|
||||
if status == 200 and isinstance(body, (bytes, bytearray)):
|
||||
await self._send_to_rvs({
|
||||
"type": "file_version_download_response",
|
||||
"payload": {
|
||||
"ok": True,
|
||||
"requestId": req_id,
|
||||
"path": req_path,
|
||||
"hash": req_hash,
|
||||
"base64": base64.b64encode(body).decode("ascii"),
|
||||
"size": len(body),
|
||||
"name": (req_path.rsplit("/", 1)[-1] or "file"),
|
||||
},
|
||||
"timestamp": int(asyncio.get_event_loop().time() * 1000),
|
||||
})
|
||||
else:
|
||||
err = body.decode("utf-8", "ignore") if isinstance(body, (bytes, bytearray)) else str(body)
|
||||
await self._send_to_rvs({
|
||||
"type": "file_version_download_response",
|
||||
"payload": {
|
||||
"ok": False,
|
||||
"requestId": req_id,
|
||||
"path": req_path,
|
||||
"hash": req_hash,
|
||||
"error": f"HTTP {status}: {err[:200]}",
|
||||
},
|
||||
"timestamp": int(asyncio.get_event_loop().time() * 1000),
|
||||
})
|
||||
except Exception as e:
|
||||
logger.warning("[rvs] file_version_download_request: %s", e)
|
||||
return
|
||||
|
||||
elif msg_type == "file_version_restore_request":
|
||||
# Eine Version als neue aktive setzen — non-destructive
|
||||
# (diagnostic schreibt den alten Inhalt + macht einen neuen Commit).
|
||||
req_path = payload.get("path", "")
|
||||
req_hash = payload.get("hash", "")
|
||||
logger.warning("[rvs] file_version_restore_request: %s <- %s",
|
||||
req_path, req_hash[:7])
|
||||
try:
|
||||
body_bytes = json.dumps({"path": req_path, "hash": req_hash}).encode("utf-8")
|
||||
req = urllib.request.Request(
|
||||
"http://localhost:3001/api/files-version-restore",
|
||||
data=body_bytes,
|
||||
method="POST",
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
def _do_restore():
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||
return resp.status, resp.read().decode("utf-8", errors="ignore")
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, e.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_restore)
|
||||
try:
|
||||
parsed = json.loads(body) if body else {"ok": False, "error": "leer"}
|
||||
except Exception:
|
||||
parsed = {"ok": False, "error": body[:200]}
|
||||
if status != 200 and "ok" not in parsed:
|
||||
parsed = {"ok": False, "error": f"HTTP {status}"}
|
||||
await self._send_to_rvs({
|
||||
"type": "file_version_restore_response",
|
||||
"payload": parsed,
|
||||
"timestamp": int(asyncio.get_event_loop().time() * 1000),
|
||||
})
|
||||
except Exception as e:
|
||||
logger.warning("[rvs] file_version_restore_request: %s", e)
|
||||
return
|
||||
|
||||
elif msg_type == "location_update":
|
||||
# Live-GPS-Update von der App (nicht an Chat gekoppelt). Wird in
|
||||
# /shared/state/location.json geschrieben, damit Watcher-Trigger
|
||||
|
||||
Reference in New Issue
Block a user