From 4feaacc7e4302e3d6e2c27c10d7e0c094fb409bb Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sun, 10 May 2026 12:49:22 +0200 Subject: [PATCH] feat(update): APK-Cache robuster + manueller 'Update-Cache leeren' Button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stefan: 'app blaeht sich auf durch heruntergeladene Update-Versionen'. updater.ts: - cleanupOldApks durchsucht jetzt 4 Pfade (Caches, Documents, ExternalCaches, ExternalDir) statt nur CachesDirectoryPath - Public gemacht + returnt {removed, freedMB} - getApkCacheSize() neu — listet count + totalMB SettingsScreen → Speicher: - Neue Sektion 'Update-Cache' mit Live-Groessenanzeige - Button 'Update-Cache leeren' triggert cleanup + Toast mit Ergebnis - Beim Mount wird die Groesse einmal geladen Auto-Cleanup laeuft weiterhin beim App-Start + vor jedem Download — der Button ist fuer den Notfall (haengender Download, alte Pfade, defekte APKs). Co-Authored-By: Claude Opus 4.7 (1M context) --- android/src/screens/SettingsScreen.tsx | 34 +++++++++++ android/src/services/updater.ts | 83 +++++++++++++++++++------- issue.md | 1 + 3 files changed, 97 insertions(+), 21 deletions(-) diff --git a/android/src/screens/SettingsScreen.tsx b/android/src/screens/SettingsScreen.tsx index 8dc4de2..52f4caf 100644 --- a/android/src/screens/SettingsScreen.tsx +++ b/android/src/screens/SettingsScreen.tsx @@ -63,6 +63,7 @@ import wakeWordService, { import ModeSelector from '../components/ModeSelector'; import QRScanner from '../components/QRScanner'; import VoiceCloneModal from '../components/VoiceCloneModal'; +import updateService from '../services/updater'; const STORAGE_PATH_KEY = 'aria_attachment_storage_path'; const DEFAULT_STORAGE_PATH = `${RNFS.DocumentDirectoryPath}/chat_attachments`; @@ -132,6 +133,7 @@ const SettingsScreen: React.FC = () => { // null = automatisch (adaptive Baseline), sonst manueller dB-Override const [vadSilenceDb, setVadSilenceDb] = useState(null); const [showVadInfo, setShowVadInfo] = useState(false); + const [apkCacheInfo, setApkCacheInfo] = useState<{count: number, totalMB: number} | null>(null); const [ttsSpeed, setTtsSpeed] = useState(TTS_SPEED_DEFAULT); const [wakeKeyword, setWakeKeyword] = useState(DEFAULT_KEYWORD); const [wakeStatus, setWakeStatus] = useState(''); @@ -220,6 +222,7 @@ const SettingsScreen: React.FC = () => { if (saved && (WAKE_KEYWORDS as readonly string[]).includes(saved)) setWakeKeyword(saved); }); isWakeReadySoundEnabled().then(setWakeReadySound); + updateService.getApkCacheSize().then(setApkCacheInfo).catch(() => {}); AsyncStorage.getItem('aria_xtts_voice').then(saved => { if (saved) setXttsVoice(saved); }); @@ -1194,6 +1197,37 @@ const SettingsScreen: React.FC = () => { )} + {/* === Update-Cache === */} + Update-Cache + + + Heruntergeladene APK-Dateien fuer App-Updates. Werden automatisch + beim App-Start und vor jedem neuen Download geloescht — der Button + ist fuer den Notfall (z.B. wenn ein Download haengen geblieben ist). + + + {apkCacheInfo === null ? '...' : + apkCacheInfo.count === 0 ? 'leer' : + `${apkCacheInfo.count} APK${apkCacheInfo.count === 1 ? '' : 's'} · ${apkCacheInfo.totalMB.toFixed(1)}MB`} + + { + const res = await updateService.cleanupOldApks(); + ToastAndroid.show( + res.removed === 0 + ? 'Update-Cache war schon leer' + : `${res.removed} APK${res.removed === 1 ? '' : 's'} geloescht (${res.freedMB.toFixed(1)}MB frei)`, + ToastAndroid.SHORT, + ); + const info = await updateService.getApkCacheSize(); + setApkCacheInfo(info); + }} + > + Update-Cache leeren + + + )} {/* === Logs === */} diff --git a/android/src/services/updater.ts b/android/src/services/updater.ts index 74d019d..790dc27 100644 --- a/android/src/services/updater.ts +++ b/android/src/services/updater.ts @@ -50,28 +50,69 @@ class UpdateService { }); } - /** Raeumt alte heruntergeladene APK-Dateien aus dem Cache auf. */ - private async cleanupOldApks(): Promise { - try { - const files = await RNFS.readDir(RNFS.CachesDirectoryPath); - const apks = files.filter(f => /\.apk$/i.test(f.name)); - let freed = 0; - for (const f of apks) { - try { - const size = parseInt(f.size as any, 10) || 0; - await RNFS.unlink(f.path); - freed += size; - console.log(`[Update] Alte APK geloescht: ${f.name} (${(size / 1024 / 1024).toFixed(1)}MB)`); - } catch (err: any) { - console.warn(`[Update] APK-Loeschen fehlgeschlagen: ${f.name} (${err?.message || err})`); - } - } - if (apks.length > 0) { - console.log(`[Update] Cleanup fertig: ${apks.length} APKs entfernt, ${(freed / 1024 / 1024).toFixed(1)}MB freigegeben`); - } - } catch (err: any) { - console.warn(`[Update] Cleanup-Fehler: ${err?.message || err}`); + /** Sucht ueberall wo .apk-Dateien herumliegen koennten. */ + private async _apkSearchDirs(): Promise { + const dirs = [RNFS.CachesDirectoryPath, RNFS.DocumentDirectoryPath]; + if ((RNFS as any).ExternalCachesDirectoryPath) { + dirs.push((RNFS as any).ExternalCachesDirectoryPath); } + if (RNFS.ExternalDirectoryPath) { + dirs.push(RNFS.ExternalDirectoryPath); + } + return dirs; + } + + /** Raeumt alte heruntergeladene APK-Dateien aus den App-Verzeichnissen auf. + * Public damit Settings den Button "Update-Cache leeren" benutzen kann. */ + async cleanupOldApks(keepCurrentName?: string): Promise<{ removed: number; freedMB: number }> { + const dirs = await this._apkSearchDirs(); + let removed = 0; + let freed = 0; + for (const dir of dirs) { + try { + if (!(await RNFS.exists(dir))) continue; + const files = await RNFS.readDir(dir); + const apks = files.filter(f => /\.apk$/i.test(f.name)); + for (const f of apks) { + if (keepCurrentName && f.name === keepCurrentName) continue; + try { + const size = parseInt(f.size as any, 10) || 0; + await RNFS.unlink(f.path); + removed += 1; + freed += size; + console.log(`[Update] APK geloescht: ${f.path} (${(size / 1024 / 1024).toFixed(1)}MB)`); + } catch (err: any) { + console.warn(`[Update] APK-Loeschen fehlgeschlagen: ${f.path} (${err?.message || err})`); + } + } + } catch (err: any) { + console.warn(`[Update] Cleanup-Fehler in ${dir}: ${err?.message || err}`); + } + } + const freedMB = freed / 1024 / 1024; + if (removed > 0) { + console.log(`[Update] Cleanup fertig: ${removed} APK${removed === 1 ? '' : 's'} entfernt, ${freedMB.toFixed(1)}MB freigegeben`); + } + return { removed, freedMB }; + } + + /** Aktuelle Groesse aller APK-Dateien in den App-Verzeichnissen (in MB). */ + async getApkCacheSize(): Promise<{ count: number; totalMB: number }> { + const dirs = await this._apkSearchDirs(); + let count = 0; + let total = 0; + for (const dir of dirs) { + try { + if (!(await RNFS.exists(dir))) continue; + const files = await RNFS.readDir(dir); + for (const f of files) { + if (!f.isFile() || !/\.apk$/i.test(f.name)) continue; + count += 1; + total += parseInt(f.size as any, 10) || 0; + } + } catch {} + } + return { count, totalMB: total / 1024 / 1024 }; } /** Bei App-Start Update pruefen */ diff --git a/issue.md b/issue.md index 95309a9..312cf7f 100644 --- a/issue.md +++ b/issue.md @@ -164,6 +164,7 @@ Wichtige Mechanismen: - [x] **Neue Frage waehrend Telefonat** ueberschreibt pending Auto-Resume — letzte Antwort gewinnt, alter resumeSound wird gestoppt - [x] **Audio-Ausgabe waehrend aktivem Telefonat** funktioniert (haltAllPlayback nur bei state-Wechsel idle→ringing/offhook, nicht bei offhook→offhook) - [x] **PcmPlaybackFinished-Event** im Native: AudioFocus wird erst released wenn AudioTrack wirklich durch ist (vorher: end()-Cap nach 0.5s → Spotify spielte 32s parallel zu ARIA) +- [x] **APK-Cache-Cleanup robuster**: durchsucht jetzt CachesDirectoryPath + DocumentDirectoryPath + ExternalCachesDirectoryPath + ExternalDirectoryPath statt nur Caches. Plus manueller Button "Update-Cache leeren" in Settings → Speicher mit Live-Anzeige der aktuellen Groesse - [x] Diagnostic-Chat: bubblige Formatierung, mehrzeiliges Eingabefeld (textarea, Enter sendet, Shift+Enter neue Zeile) - [x] Adaptive VAD-Schwelle: Baseline aus den ersten 500ms Mic-Pegel, Stille = baseline+6dB / Sprache = baseline+12dB - [x] Max-Aufnahmedauer konfigurierbar in Settings (1-30 min, Default 5 min) — laengere Diktate moeglich